62 lines
13 KiB
Markdown
62 lines
13 KiB
Markdown
# Behavior multi-selection design review — 2026-09-23
|
||
|
||
Read-only review of published `b8685416f56e53e30bd11ecf93a54cea10ac8458` for issue #29. This note is a local-only proposal until the owning session publishes it. No runtime, build, GUI, or acceptance claim is made. Process, Scenario, and State are all within the requested usability outcome; architecture-only support is not equivalent completion.
|
||
|
||
## Verified implementation boundaries
|
||
|
||
| Surface | Identity and current gesture | Source at reviewed commit |
|
||
| --- | --- | --- |
|
||
| Process | Local occurrence/relation UUID; expanded reusable contents additionally require `OccurrenceEndpoint { references, occurrence }` and owner. Dragging the selected handle changes `steps` order only within one derived graph column. Links determine columns. | `crates/oa-desktop/src/behavior/topology/direct.rs:5`, `:29`, `:112`, `:448`, `:721`; `crates/oa-desktop/src/app/process_canvas.rs:145` |
|
||
| Scenario | Lifeline/occurrence UUID under a scenario; occurrence rows also identify their immediate sequence/operand parent. Drag changes semantic ordering, constrained to the same parent; the candidate is validated. A stationary row click selects on release. | `crates/oa-desktop/src/behavior.rs:1171`, `:1217`, `:1909`, `:1985`; `crates/oa-desktop/src/behavior/scenario.rs:1091` |
|
||
| State | Semantic UUID and exact placement UUID are distinct. Vertex movement mutates persisted geometry and includes projected descendants. State-action rows and transitions are relation UUIDs. | `crates/oa-desktop/src/behavior/state_canvas.rs:136`, `:983`, `:1359`; `crates/oa-desktop/src/behavior/state_edit.rs:131`, `:533` |
|
||
| Navigation | Existing `Surface`/`Target` describes saved placements, state rows, qualified process occurrences, preview links and scenario objects. Its navigation highlight is explicitly not an editing target. Coordinate with #31 before touching its files. | `crates/oa-desktop/src/diagram_usage.rs:105–156` |
|
||
|
||
The shared viewport admits keyboard events only while focused, keyboard-enabled, and outside pan/space gestures (`behavior/canvas_workspace/viewport.rs:327`). `keyboard_owner_enabled` excludes modal menus, confirmation, reference inputs, workflow, and usage chooser (`behavior/canvas_workspace.rs:17`). Dirty inspector/source drafts and pending behavior editors add mutation guards (`app/diagram_removal.rs:24`; `behavior/state_view.rs:178`). Pointer state already captures document, owner/diagram, revision and epoch, and cancels on relevant context/view changes. Preserve these lifetimes.
|
||
|
||
Delete currently has materially different meanings: persisted placement Delete hides that placement; Shift+Delete targets its semantic object. Derived Process/Scenario Delete removes the local semantic occurrence or relation, preserving reusable definitions. Qualified Process preview contents cannot be deleted from the caller (`app/diagram_removal.rs:55–110`, `:170`; `app/process_canvas.rs:96–113`). This distinction must remain visible in any group action.
|
||
|
||
## Recommended minimal coherent contract (proposal)
|
||
|
||
Use a common selection interaction and family-specific command planners. Do not add free geometry to derived Process/Scenario diagrams as an accidental consequence of this feature: doing so would require a separate persisted-layout specification and source/API parity decision.
|
||
|
||
1. Selection belongs to `(document identity, surface identity)`, contains typed visual targets in stable order, and has one primary target for the existing inspector. Semantic UUID alone is insufficient for saved placements or repeated Process previews. A selected target may be inspectable without being movable/deletable. Invalid or newly hidden targets are pruned when the document/view changes; navigation to another owner clears selection (Back may deliberately restore a separately captured selection).
|
||
2. Plain click an unselected target replaces the set. Shift+click toggles membership; adding makes it primary, removing the primary selects the most recent remaining member. Track modifiers in the focused input path, not through a global shortcut that also fires in text fields. Shift selection must precede endpoint/bend/reorder handles and must never reconnect or start a move.
|
||
3. Press on a selected movable target preserves the set so drag moves the group. A stationary plain release may collapse to that target; decide once across families and test it. Right-click a selected member preserves the group; right-click an unselected item makes it the sole member before presenting commands. A stationary background click clears selection. Existing background pan must not clear it merely because the pan started.
|
||
4. Paint every member, distinguish the primary, and show a concise count and available group action. Single-object property editing remains explicitly the primary object's editor. Do not imply that changing its name, type, guard, or operand edits the set.
|
||
5. Group operations capture the complete immutable target set, primary, document, surface, revision and gesture epoch. Preview and commit use that same snapshot. One successful gesture is one document transaction/undo entry; Escape, stale context, modal entry, tool switch, focus loss, invalid release, or validation failure produces no partial edit. Preserve selection after successful mutation and reconcile after undo/redo.
|
||
6. Capability admission evaluates the entire selection, including qualified/read-only members. Unsupported mixtures must give a specific reason; silently acting on only the primary or silently omitting unsupported members is not a group action. If a supported action intentionally affects only nodes in a nodes-and-edges set, label it "Move selected nodes" and derive attached routes; never move a selected edge's endpoints implicitly.
|
||
|
||
## Family-specific group operations (proposal)
|
||
|
||
**State/saved geometry:** Translate all selected root placements by one world-space delta. Expand the union of each root's descendant placements from the original model; parent plus selected child moves the child exactly once, including folded descendants. Preserve semantic ownership and all relative offsets. Compute route translation against the complete placement-delta union once. `Command::Batch` containing independent `MoveNode` calls is not sufficient: each `MoveNode` currently computes its own route delta map (`oa-core/src/session.rs:771–806`), so a route between two independently moved roots does not receive the same treatment as one whose endpoints move in one operation. Prefer a small shared core group-move command/planner used by both saved architecture and State. Coordinate geometry interaction with #16; group resizing is not implied by group movement.
|
||
|
||
**Process:** Reorder a selected group of local contributions as a stable block within their existing derived column. Retain their prior `steps` order, remove all selected IDs, and insert before the chosen unselected contribution. Drop inside the selected block is a no-op. Require one owner and one column for all selected contributions; reject qualified previews, links, and cross-column sets for this operation. Preserve all sequence/contextual link UUIDs, endpoints and guards. Presentation order must not become new topology. Explicitly distinguish the legacy ordered-path representation, whose `steps` order can carry behavioral meaning; the planner must validate the complete candidate and guidance should say "Reorder contributions", not "Move freely". Add block movement through both pointer and bounded earlier/later controls so small Fit views remain usable.
|
||
|
||
**Scenario:** Reorder selected occurrences as a stable block inside one immediate sequence/operand parent, with the same remove-then-insert algorithm and no-op rule. Lifelines are not occurrence rows and cannot participate in this operation. Nested fragments/branches move with their owned contents; do not separately reorder a selected descendant when its ancestor is moved. Cross-parent selection may remain useful for inspection/deletion but cannot silently migrate between operands. Run existing full-candidate validation once after the proposed order, preserving call/reply, activation, lifecycle, and fragment semantics. Invalid causal order returns an actionable reason and retains the original sequence. Do not claim pixel-distance preservation or arbitrary x/y movement in this temporal view.
|
||
|
||
**Bulk removal across families:** Reuse the current semantics with an aggregate target/impact preview and one atomic transaction. Deduplicate objects and descendants so overlapping selections are not applied twice. For persisted diagrams, expose "Hide selected placements" separately from "Delete selected model objects"; never silently delete an edge semantically in an otherwise placement-only hide action. For Process/Scenario expose "Delete selected occurrences/relations" and preserve definitions. Read-only qualified preview contents require opening the definition; selecting the outer reuse occurrence allows deleting that use. The preview must enumerate actual dependency effects (e.g. deleting a participant clears occurrence participant references; deleting a fragment removes its owned branches/steps) rather than merely displaying a count.
|
||
|
||
Useful later group actions discovered from these workflows are hide/show placement sets, align/distribute geometry-only nodes, reorder block earlier/later, duplicate an occurrence block with identity/reference remapping, and applying genuinely shared editable properties. Alignment is not meaningful for derived temporal/topology layouts. Duplication and batch property editing each need their own typed semantic contract; do not expose existing singleton actions under a misleading multi-selection label.
|
||
|
||
## Alternatives and staged dependencies
|
||
|
||
- **Uniform free drag everywhere:** visually simple but breaks the present Process topology/Scenario temporal representation or requires new persisted layout models. Reject for this change unless explicitly undertaking that broader semantics work.
|
||
- **Geometry surfaces only:** quickest, but leaves the user's family-wide inability to select/act on groups unresolved. A staged delivery can name this limitation; it cannot close the full outcome.
|
||
- **Common selection plus capability planners:** recommended. Reuses transaction, identity and input guards while respecting derived layouts. Costs a small shared selection layer plus independently testable State, Process and Scenario planners.
|
||
|
||
Implementation dependency order: (1) selection identity/primary/context reducer and gestures; (2) union-based core geometry movement and State/saved adapters; independently, (3) stable-block reorder planners and Process/Scenario adapters; (4) aggregate removal with impact/confirmation lifetime; (5) realistic OA/SA native validation, undo/save/reopen/source parity, fresh review. Sharing selection does not require merging navigation-only `Focus` with mutable selection or generalizing every diagram renderer.
|
||
|
||
Choices to settle explicitly: stationary click on an already selected member retains group versus collapses on release; whether Shift+click toggles or only adds (toggle recommended); exact shortcut for Select All when canvas owns focus; whether marquee replaces existing blank-left-drag panning (retain current pan and defer marquee unless a deliberate cross-family gesture decision is made); whether bulk deletion is delivered in the same milestone or left as a specifically tracked gap. None requires changing semantic ownership through a drag.
|
||
|
||
## High-risk falsifiers / acceptance examples
|
||
|
||
- Select State parent plus child plus an independent node with an edge between roots. Move once: each placement and eligible authored bend translates once; other diagram placements and semantic parents are byte-equivalent. Undo/redo and source round-trip restore exact geometry.
|
||
- Reuse one subprocess twice and Shift-select the same inner occurrence in both previews. Highlights remain distinct; group deletion/reorder cannot mutate the reusable definition or confuse the two callers.
|
||
- Select noncontiguous Process contributions in one column, reorder together, and compare every link and guard. Cross-column drop rejects with no partial change. Exercise a legacy exchange-only ordered process as well.
|
||
- Select Scenario call/reply together and reorder; also move reply alone before call, across an operand, or across create/delete lifecycle boundaries. Valid block succeeds once; invalid candidate leaves sequence/model/source/history unchanged. Fragment plus descendant never duplicates or reparents a step.
|
||
- Shift-click selected transition endpoint, bend handle, State action row, Scenario message line, and Process reorder handle. Membership changes without starting reconnect/bend/reorder gestures. An ordinary drag still performs the established operation when selection is singular.
|
||
- Focus inspector text, open modal/usage chooser, switch document with same project UUID, change owner, scroll/zoom, lose window focus, or start source edits during group gesture. No queued release/Delete can commit against stale context; typed text shortcuts remain local.
|
||
- Select mixed nodes/edges or local/qualified-preview items. UI tells the truth about action capability and never silently operates on the primary alone. All visible selection states remain distinguishable without hue alone.
|
||
|
||
No code edits were made as part of this review. These cases are proposed evaluators, not passing test results.
|