Files
ARCADIA-Studio/harness/experiments/diagram-resize-foundation-20260912/design.md
T

26 KiB
Raw Blame History

Direct diagram resize investigation — 2026-09-12

Status: independent read-only design for Gitea issue 16, delegated by root. This report is a candidate for root synthesis and fresh criticism, not an accepted specification or implemented capability. Production, user-owned input, Git/index, Gitea, compiler, native applications and WSL were not changed. Only this report was written.

Source was inspected on codex/behavior-authoring-20260910; HEAD read back as c3c2d0145340e8960341e97f04edf1898d36a7c5. Root is concurrently integrating its separately owned Delete work, so this is a source-mapping report, not frozen executable evidence. Root owns shared-remote instruction and ownership reconciliation. No external research was needed beyond the existing local Capella study.

Recommendation and decision criteria

Provide ordinary eight-handle resizing of selected rectangular diagram placements, with precise live projection, fixed opposite edges, stable world coordinates for authored child placements, one atomic accepted edit, and exact reopen/source/API parity. Container resize never implies allocation, decomposition, deployment, reparenting or rearrangement of children. The full eight-handle behavior belongs in feature acceptance; an east/south/southeast-only prototype is useful for testing geometry but does not by itself close the modern direct-resize request.

Criteria come from PROJECT_GOAL.md: reduce engineers' cognitive load, make direct diagram editing consistent, retain useful visual nesting, preserve full graphical/textual equivalence, and support maintainable implementation. The immediate user problem is arranging entities, activities and other objects for comprehensible layout/routing. A size form alone would not solve the direct manipulation problem.

Candidate Ergonomic and semantic consequences Decision
Numeric properties only Precise but separates the hand from the object and requires repeated value guessing. Useful accessible alternate entry, insufficient alone.
Eight handles scale/translate all child coordinates with the frame Familiar in drawing/group-scale tools; surprises an engineer who is making room around existing work and changes many route/port relationships. Hidden descendants make the effects harder to predict. Reject as ordinary resize. A separately named future group-scale operation would need independent evidence.
Eight handles freely shrink the frame and clip or let children overflow Pointer follows freely, but visually nested semantics become ambiguous and header/action content can disappear. Reject.
Eight handles keep child placements stationary and clamp to required content/port/header geometry Familiar frame editing; frame grows around work and can shrink to a visible minimum without moving the work. North/west handles give essential control over which margin grows. Recommend.
East/south/southeast only Sufficient to test persistence and positive-edge geometry; engineers must move the whole group to add space on the left/top, which also moves its contents/routes. Foundational experiment only, not completed feature acceptance.
Desktop-only resized rectangles Fast demonstration but source, API, reopen, undo and SVG disagree. Reject.

The main unresolved implementation choice is how to derive an exact constraint solution from the current projection's two-stage port capacity pass. It must be settled by the foundational experiment below, not hidden behind an approximate UI rectangle.

Confirmed current behavior and authoritative sources

  1. crates/oa-core/src/model.rs:1484–1560: Diagram.nodes holds NodePlacement { id, alias, element, position, size, collapsed, port_anchors }. Position and size are persisted; placement identity is distinct from semantic identity. A semantic element occurs at most once in one diagram, but can have independent placements in multiple diagrams. docs/language.rst:64, and its diagram/node section around 1100–1200, already state placement does not determine parent/allocation/realization and coordinates are absolute diagram coordinates.
  2. crates/oa-core/src/session.rs:34,531: EditNode can edit size and position today. It is an exact record edit; it does not translate descendants or route pins. MoveNode at 771 walks visual descendants when requested and translates both-ended group route pins. A resize must not fall through that command. Node IDs/aliases/element/anchors must be copied unchanged.
  3. crates/oa-core/src/validation.rs:733–827: placements require existing compatible elements and unique placement membership; x/y/width/height must be finite and bounded by MAX_COORDINATE, and width/height positive. It does not currently validate a graphical header/content minimum. Positive tiny authored sizes remain structurally valid data, even when projection enlarges them. Do not turn a new pointer minimum into an incompatible schema rule.
  4. crates/oa-core/src/projection.rs:75,82,275,538: one core projection drives normal drawing, hit testing, Fit, SVG and movement. Projection first derives visual parents from semantic ancestors; Allocation may instead group by effective performer/allocation summary; Traceability deliberately has no visual parents. Visual parenthood is not discovered from rectangle overlap. Projection enlarges and can shift ancestors to enclose descendants with 24-unit left/right/footer margins and a 64-unit header (state-specific header in dedicated state views). Stored position/size therefore need not equal visible bounds.
  5. crates/oa-core/src/projection/port_layout.rs:131,161–264: there are two port size effects. A semantic-side count grows base owner size (vertical: 76 + 28*(count+1)); automatic side choices then depend on geometry and connection peers, followed by capacity growth (36 + 28*(count-1)), another enclosure pass, and final port positioning. Moving a side during resize can change automatic port-side choice/capacity. A simple min-width/min-height copied from the first pass is not a complete constraint model. Manual anchors preserve side and fractional offset; closely packed manual anchors degrade spacing rather than relocating authored anchors.
  6. crates/oa-core/src/projection/state.rs:24–144: stable State/Mode are rectangles (minimum width 184); Machine/Region rectangles have width minimum 224. Height reserves dynamic entry/exit/do-action and internal-transition rows. Pseudostates have fixed body sizes regardless of NodePlacement.size: Initial 16x16, Junction 10x10, Fork/Join 8x36, Choice/History 28x28, Final/Entry/Exit/Terminate 24x24. Entry/Exit symbols are semantically owned by a Region but snap to their containing State/Machine boundary; they do not enlarge its content envelope. Missing containing boundary does not invent a placement.
  7. crates/oa-desktop/src/behavior/state_canvas.rs:48–99 and symbol_labels: dedicated state editing has its own ordered pointer gestures, scene cache, dynamic symbol-label geometry and clipped guidance strip. Its existing press/event contracts do not contain resize. behavior/state_edit.rs has a move event/planner, again not size editing. Symbol labels and enlarged pointer targets are derived UI, not independently sized persisted nodes.
  8. crates/oa-core/src/model.rs:1500–1547, projection.rs:167–222, routing.rs, harness/release-routing-contract.md: route pins are world coordinates; label is fraction along the computed route plus relative offset; ports are semantic child references anchored on an owner placement, not independent placement rectangles. Resizing changes actual endpoints and obstacles, so recompute routes. Preserve stored pins/labels exactly. Raw EditNode is expressly exact and must not implicitly translate pins. Fold-promoted endpoint routes remain stored and resume on expansion.
  9. crates/oa-desktop/src/document.rs:315–326: Document::execute prepares a validated candidate, updates/compiles source with the identity map, then commits. Source preparation failure occurs before adoption. Use this path and one command; do not modify the accepted snapshot during motion. docs/api.rst and existing transaction API already support normal EditNode. This resize slice needs no new persistence schema or API command.
  10. crates/oa-desktop/src/diagram.rs:46–78,2976 (the latter message handler is in app.rs): architecture canvas and its legacy MoveNode message have no resize gesture. Legacy App movement calls apply(), which can apply a draft; this is not acceptable resize admission.
  11. allocation_drag.rs:449–619,700–749, app/allocation_drag.rs, diagram/work_drag.rs, and harness/release-activity-allocation-drag-contract-v2-20260911.md: allocation-body dragging freezes accepted target geometry/context, may change allocations, carries semantic work/placement closure and exact preview/final validation. Geometry is frozen for target resolution during a single gesture, not an immutable model layout. Existing work-body paths must retain their semantics; a resize handle must enter a separate layout-only gesture before that body path. The 2026-09-12 feedback amendment keeps destination identity in a toolbar slot, not a duplicate canvas label.
  12. harness/release-capella-workflow-benchmark.md:91 records actual Capella resizing of a reused-chain container to reveal nested content without changing the referenced object. harness/release-capella-direct-manipulation-gap-20260911.md separately distinguishes arranging a use, reparenting a definition, allocating work, and transferring an occurrence. These support stationary-content resizing; they do not prove every proposed Studio handle policy.

Proposed contract

Scope and identity

Eligible objects are visible selected rectangular NodePlacements in existing architecture projections and dedicated state diagrams: entities/actors/components, activities/functions including allocation summaries, ordinary dictionary/capability cards, State/Mode/Region/Machine, and other objects already drawn as resizable rectangles. Use projection/presentation shape, not a small hard-coded set of entity types. A rectangular representation of a state in a general architecture view is distinct from its fixed symbol in a dedicated state view.

Handle attachment and commits target (document instance, accepted revision/snapshot, active diagram, placement ID, semantic element ID). Resize one placement in the active view. The complete final Project differs only in that placement's admitted position/size fields. All semantic records, allocations/roles/parents, identities, other diagrams, other stored node fields, routes, anchors, traces, artifacts and view filters stay byte/value equivalent as appropriate. No placements are materialized by this existing-view operation.

Fixed state symbols do not show rectangular resize handles. Ports keep their existing repositioning gesture; route bends and route labels keep their existing editing gestures. This is an explicit shape/persistence distinction, not treating all graphical items as immutable. Independently sized pseudostates, symbol labels or port glyphs would require a reviewed shape/label contract before promising them. Fork/Join bar-length editing may be valuable, but current projection ignores stored size; it is not honestly delivered by writing size alone.

For a collapsed rectangle, expose east/west width resizing; retain the complete stored expanded height and collapse flag. Show concise guidance to expand for height resizing. Do not write the projected collapsed height (normally 94 before port minima) back over expanded height. A folded body may still have port-driven effective height; width changes preview that exact derived height.

Geometry and fixed edges

Capture the visible projected body rectangle R0=(L0,T0,R0,B0) at press, together with the original stored placement N0. Pointer delta is measured from the ordered native press in the captured transform, in diagram units. Compute every candidate from this baseline, never cumulatively from the last preview.

Eight handles move these edges: W=L, E=R, N=T, S=B; corners move the corresponding pair. Every opposite edge stays fixed exactly. Edges on an inactive axis stay fixed. Crossing the opposite edge clamps at the permitted minimum and never flips/rotates the object or changes handle identity. Snap moving-edge deltas in the ordinary 10-world-unit increments, so existing off-grid geometry does not jump at press or a zero delta. Clamping may land off-grid when that is the exact content minimum. Keyboard exact numeric sizing need not snap.

Resize is frame editing. Existing child placement positions and sizes, including folded descendants, stay stored at their world coordinates. West/north changing the selected top-left must not call MoveNode or add that delta to descendants. The shared projection may adjust derived automatic ports/child sizes and ancestor enclosures; preview the whole affected scene. Entry/Exit symbols follow the resized containing boundary through their existing projection rule while retaining their authored coordinates. No semantic parent/allocation change follows from one box geometrically crossing another.

Expanded containers cannot shrink through visible nested content or reserved header/actions/rows. For fixed projected content envelope C, the familiar first constraints are:

  • W: L <= min(R0 - minimum_width, C.left - 24); E: R >= max(L0 + minimum_width, C.right + 24).
  • N: T <= min(B0 - minimum_height, C.top - header_height); S: B >= max(T0 + minimum_height, C.bottom + footer).
  • Empty content omits its corresponding C terms. State boundary Entry/Exit vertices are excluded exactly as in core enclosure.

Example: visible parent [100,100]–[500,400], child [160,200]–[360,300], ordinary header 64/margin24. West cannot pass x136; east cannot pass x384; north cannot pass y136; south cannot pass y324, subject also to node/port minima. Dragging W to x50 yields parent [50,100]–[500,400], child unchanged. Dragging N to y0 yields [100,0]–[500,400], child unchanged. Dragging NW toward [200,250] clamps to the content/minimum boundary; it must not move the child down/right or move fixed R0/B0.

Those formulas are explanatory lower bounds, not a sufficient implementation with geometry-dependent ports. Each admissible candidate must pass the real core projection, and the selected projected rectangle must equal the offered rectangle with the captured fixed edges. Any necessary secondary extent change on an inactive axis must be exposed as a constraint/refusal (for example “Ports need more height; resize a corner”), not silently changed by an east/west-only drag. Corner gestures can change both dimensions.

Proposed pointer floor for ordinary rectangular cards is 184x94 world units, strengthened by the existing state/content/port constraints. This is a reversible UX assumption requiring native evaluation, not a fact or new source-validation rule. Kind/header text must remain within the drawn card at that minimum; current paint_node elides the name but gives the type header infinite max width. Measure the actual longest OA/SA labels and summary headers before accepting 184. Do not use full user name length as an unbounded minimum; enlarge to reveal more text, preserving concise labels and full identity in details.

Persisting exact visible edits

Enclosure can make N0 smaller or displaced relative to R0. A successful expanded resize writes the selected placement's final visible top-left and size into its existing record. It is intentional normalization of that one edited frame, needed so later shrink operations do not encounter its stale authored origin/floor. No normalization occurs on press, stationary click, below-threshold motion, canceled gesture, or a constrained result visually equal to R0. An authored/effective mismatch alone is not a reason to add history.

A folded resize preserves N0.height and collapse flag; only the final visible width and admitted x are persisted. Other stored fields remain N0. Document that collapsed/port/content projections still have derived extents; exact persistence means the reopened scene equals the accepted preview, not a false claim that every stored node has exactly its projected dimensions.

The candidate is a cloned Diagram over accepted semantic Project/ViewIndex, not a mutable Project updated per pixel. Reproject and reroute it. At release, regenerate the plan from the baseline plus final ordered pointer, prepare its EditNode, compare the entire candidate Project with exactly the expected single-record edit, and confirm the final projected/routed scene matches the final preview. Execute through Document once only after these checks. Refresh the selected placement's clean details without changing semantic selection or running Fit. All errors preserve the accepted model/source/history/camera and a useful explanation.

Existing ancestor enclosures are derived: resizing a child may grow or shrink an ancestor down to its own authored floor. Show this in preview. Do not persist normalization to all ancestors or automatically move siblings; that would turn one resize into an implicit layout operation. Overlap with unrelated rectangles is presentation, not ownership. Show routing/overlap consequences, but do not copy allocation-drag's blanket no-new-overlap refusal: a general layout tool must allow engineers to arrange overlapping geometry and recover. Exact content/header containment is a stronger invariant than unrelated-card overlap.

Admission, lifetime, feedback and keyboard

Show handles only for the selected eligible placement. Handle glyph/hit targets are screen-sized at every zoom and positioned outside the body so ports at the border remain reachable. Explicit hit precedence: overlay/tool ownership first; actual port or active connection/route controls keep their gesture; then visible resize handles; then ordinary body/allocation drag. If targets overlap at extreme zoom, select nearest visible control center with deterministic ties; enlarged invisible slop must not override a different visible control. Native evaluation must prove both a port and a neighboring resize handle remain discoverable/reachable. A resize cursor and small “Resize · width × height” feedback make the distinction observable.

Use clean selection/admission without apply(). Dirty properties, source, behavior draft or input-owning overlays prevent arming resize and preserve drafts/focus. Use an App-registered monotonically unique token plus full context, input epoch, captured bounds/pan/zoom and snapshot. Retire on owner/diagram/document/revision change, modal or tool roundtrip, dirty draft transition, focus loss, Escape, camera/Fit or actual canvas/window bounds change. Even equal-value roundtrips cannot resurrect a token. Press/move/release may arrive before a redraw; preserve native event order and permit at most one commit per token. An old release cannot cancel a newer gesture. Outside-canvas release cancels; no auto-unassign, reparent or hidden fallback move.

Show the exact candidate node, affected routes/ports, and constrained minimum reason during motion. A constrained edge stays at its minimum; neither the selection nor whole canvas moves to keep it under the cursor. Do not require the entire selected header to fit in the viewport to allow a resize: that allocation-drag restriction would prevent useful editing of a large existing container. Keep camera stable; user can pan/Fit after finishing. Escape returns to the unchanged accepted scene.

Provide an accessible context action “Size…” for the selected rectangle with width/height (width only when collapsed), full selected identity and explicit minimum explanation. It uses the same pure planner, fixed top-left and transaction invariants. Eight pointer handles remain required; numeric controls are an alternate means of accurate/keyboard operation, not a replacement. Enter accepts once, Escape cancels; invalid input retains the draft and original scene.

Foundational experiments before UI implementation

These are required proposed gates, not executed evidence. E/S/SE is a bounded experiment for the first row, then full eight handles must pass.

  1. Can an exact candidate be produced with current projection? Build a pure planner fixture using EditNode and project_diagram. Start with empty leaf; auto-enclosed performer; nested work parent; mixed allocation-summary work; collapsed descendants; State with dynamic action/internal rows; geometry-dependent input/output/neutral ports and crowded manual anchors. Compare direct formula only, bounded monotone minimum refinement, and extracting shared core constraint facts. Measure convergence and confirm fixed-edge equality. A tentative refinement enlarges only toward active moving edges when projection reveals extra minimum extent; fixed-edge or inactive-axis violations cannot be silently repaired. Preserve a last admissible ghost while showing refusal, but a routine feasible case rejected by a chosen iteration cap is a failing evaluator and reason to revise the solver, not a successful feature. Do not fork a second UI-only port/enclosure algorithm.
  2. North/west falsifiers. Start with stored parent [200,200], size224x94 whose child projects its visible top-left to [100,100]. Press/zero-delta must be clean. W/N/NW resize uses [100,100] as baseline; fixed right/bottom remain exact; children and pins stay unchanged. Shrink to content bounds, then reverse the pointer and expand. Repeat with off-grid coordinates, nested allocated work, hidden grandchildren and parent absent from a partial view. Verify no cursor-jump and no group translation.
  3. Ports and states. On each side, manual offset0, .25, 1 retains side/offset while world endpoint follows the new boundary. Automatic ports may choose new sides, but semantic endpoints stay exact and candidate preview equals final. Resize State left/top/right/bottom around Entry/Exit and deep Regions; body labels remain readable, symbols remain on the containing boundary, routes and separate labels follow actual geometry. Fixed symbols never imply persisted resizability.
  4. Atomic persistence. One successful gesture gives one undo entry. Undo/Redo, save/reopen, source export/reparse and JSON/API transaction replay reproduce complete Project and diagram geometry. Included source files retain ownership/comments/trivia through normal update path. A source preparation failure leaves both representations/history unchanged. Other view placements/routes, all semantic records and anchors compare equal. Selection remains the semantic element plus exact placement.
  5. Lifetime/input. Fast press→motion→release without redraw; release twice; pointer exits; Escape; dirty source/property/behavior; focus/modal/tool/Fit roundtrips; document replacement with equal values; stale release after newer press. No draft auto-apply, unrelated selection, zero-delta history, allocation creation/removal or hidden MoveNode fallthrough. Resize at min/max coordinate boundaries cannot produce NaN/infinite/unbounded projected rectangles.
  6. Actual engineer tasks. OA: enlarge an entity on the left while its activities stay in place, shrink a work parent around its children, route an interaction through the gained margin, resize the child, undo/reopen. SA: resize System/Actor/function with manual and automatic ports, confirm allocated work remains assigned and readable. State: make room around nested Regions and action rows, then shrink to minimum. At minimum and large windows, several zooms, every handle must be reachable with corresponding resize cursor; dense edge/port neighborhoods and partially offscreen large containers must work. Verify keyboard size entry, cancellation and failure recovery. Screenshots alone are insufficient.

Process/scenario gap and ownership boundaries

Existing process/scenario scenes are not diagram-placement consumers. behavior/topology.rs:988 constructs ranks and geometry from contributions/sequence links/legacy order; behavior.rs:1214 collects lifelines and later computes uniform x positions. These scenes do not acquire stored dimensions merely because NodePlacement has a size field. Current ProjectionKind has no owner-bound process/scenario placement projection. The existing release-capella-direct-manipulation-gap-20260911.md recommends an owner-bound persisted behavior-view contract with independent views and first-edit materialization. That prerequisite includes identity, source/REST, delete/reidentify, validation and exact occurrence/reference addressing.

Issue 16 can deliver honest resize for existing persisted architecture/state rectangles while explicitly tracking process node sizes, scenario lane widths and frame sizes as the behavior-layout dependency. This is staged delivery, not full closure of all direct-editing requirements. Do not add desktop size maps, store process definition IDs instead of occurrence/view identities, equate frame width with nonexistent persisted coverage, or invent arbitrary scenario y coordinates. Empty/missing authored behavior views should remain clean deterministic derived scenes until an accepted first placement edit materializes a reviewed view.

Suggested file/interface partition for implementation:

  • Pure shared desktop resize planner in a new focused module: baseline, Handle, candidate validation, expected single-record command and exact preview. No semantics or accepted-document mutation. Use shared core projection/routing; a needed shared constraint extraction belongs in core projection and is separately reviewed.
  • Architecture canvas/App owner: handle drawing/hit priority, ordered native events, token/admission, normal document execute and selection. Coordinate diagram.rs, app.rs, views.rs changes with root; preserve allocation and Delete owners' paths.
  • State canvas/App owner: adapter to the same planner/lifetime contract, state guidance/labels and active-diagram selection. No separate approximate geometry solver.
  • Core/source/API owner: normally no schema/command addition. Review only constraint extraction and normative clarification if needed; existing EditNode transport must be demonstrated. Behavior-view schema is a separate prerequisite coordinated with its existing owner.
  • Root: acceptance decision after a fresh independent critique of eight-handle anchoring/content/ports/folding, then isolated builds/native/platform gates and issue/release coordination. This report claims none of those outcomes.