Files
ARCADIA-Studio/harness/experiments/canvas-editing-followup-20260912/activation-design.md
T

14 KiB
Raw Blame History

Issue 21: nested call activation design and evidence

Advisory contribution by /root/nested_call_design, 2026-09-12. Root owns issue 21 and integration; baseline supplied by root: f7d84fa, codex/behavior-authoring-20260910. Read current root sources without editing them. No native input, compilation, tests, Git changes, remote coordination, or production implementation performed. Proposed checks below are not passes. The user-owned PROJECT_GOAL remains untouched.

Finding

The reported four-message model Call1 L1→L2, Call2 L1→L2, Reply2 L2→L1 reply_to Call2, Reply1 L2→L1 reply_to Call1 is already expressible. The core does not require stack-order returns. docs/language.rst:675 and oa-core/src/validation.rs:1672 require exact call identity, guaranteed-before order, reversed endpoints and no multiply executable duplicate reply. A rendering fix must not impose a new LIFO constraint or change correlations.

The direct defect is scene projection and paint geometry:

  • oa-desktop/src/behavior.rs:1295–1322 collects calls from the row BTreeMap, hence UUID order, and invokes activation_sequence with each receiver's identical lane x. It has no concept of overlap/depth.
  • activation_segment at 1601 creates all bars at x-4, width 8. Every overlapping call has the exact same horizontal footprint. Closed calls also append a width-14 cap at that same x. The last UUID-painted bar can obscure either the inner or outer call. Sorting alone cannot make two same-x bars readable.
  • occurrences at 1463 routes messages to lane centers, including 24-pixel self-message drops. activation_sequence starts a self-call at its send row y instead of its actual receive point y+24. A self-reply ends an activation at send row y; that is appropriate for the callee, whose execution ends when the reply is sent.
  • draw at 2060 paints activation boxes with the generic rounded-card path, then lifeline guide strokes and messages over all boxes. A lifeline therefore runs through a filled execution bar. Moving bars without defining draw roles/layers leaves that defect. hit shares generic BoxItem records; thinner exposed nested shoulders need hit tests against actual visible bar geometry.

The user's .local/gitea-issue21.png shows two leftward calls and an incomplete-looking second execution (ellipsis). It does not expose exact reply_to values or the final reply. Do not diagnose wrong correlation from this crop, nor assume every visible Reply belongs to the most recently drawn Call. Inspect the saved model when reproducing it.

Comparable primary evidence

The existing local harness/release-capella-scenario-benchmark-20260910.md established Activity/lifeline creation and a dragged LOOP only. It did not exercise nested calls. It cannot support a claim that nested activations have now been reproduced in installed Capella.

The official Sirius sequence user guide depicts recursive execution rectangles on lifelines and on executions, and its complex-message example includes a self-call nested inside an active receiver. This supports offset execution rectangles and meaningful send/receive anchors as the comparable visual pattern. The specifier guide configures execution start/end finders and recursively reused execution mappings. These are source/documentation observations, not a new native Capella test.

Capella Interaction.ecore at 5537297 represents Execution as a TimeLapse covering an InstanceRole, with start and finish concepts. Studio currently derives call bars from occurrences and has no standalone execution or thread record. Copying Capella's model wholesale is unnecessary for this bounded defect; offset placement must remain a derived display, not a new ownership or execution-stack assertion.

Call identity comes only from the Call occurrence UUID. Its responding messages come only from Reply.reply_to. Labels, exchange identity and row adjacency never establish correlation.

Keep the existing branch-aware correlation traversal as the semantic starting point. First produce typed activation segments containing call ID, receiver lifeline, segment start/end, enclosing branch path and termination kind (reply / branch boundary / unresolved end). Then assign horizontal placement, then route final message endpoints, then draw/hit against those final shapes. Avoid deriving semantics back from screen rectangles. Keep raw row order separate from final send/receive endpoint y.

Prefer a dedicated ActivationSegment/ActivationLayout over anonymous title-empty BoxItem conventions. Expose the same resulting rectangles and call/reply anchors to painting and hit testing. A local module behavior/scenario_activation.rs is a bounded home; this needs no persisted schema change.

Use a fixed receiver-relative rightward offset for nested bars regardless of the direction from which a call arrives. A proposed starting style is width 12 and offset 6 world pixels, square corners, with outer bars painted before inner bars. These dimensions are a design candidate requiring minimum-scale visual QA, not an assertion that six pixels is already usable. Make corresponding closed cap edges consistent with the bar instead of the existing oversized generic capsule. At depth >0 there must be an exposed shoulder and separately visible start and end edges. Selection cannot be the only way to reveal an inner call.

Depth/columns are visual overlap separation, not inferred synchronous thread ownership. Assign deterministically in semantic event order (with UUID used only as an otherwise-unneeded final tie-breaker), independently per receiver lifeline and compatible branch path. Retain one column for a call through its segments; reuse a column after that call's relevant closed extent. For well-nested complete intervals, the inner call occupies the next column. Crossed complete intervals also require distinct columns, but their actual termination y values remain crossed. Never shorten or re-correlate one to make a pretty nesting tree.

Calls wholly in different ALT operands do not consume each other's depth. Calls wholly in different PAR operands are also not evidence of nesting: branch layout is presentation, not chronology. Inherited outer activations may have separate segments in each operand and need to be compared only against activations in that operand. Reuse the existing ALT/PAR/OPT/LOOP open-path outcomes; add a regression before refactoring them. If a pure rectangle-overlap allocator is used, verify it cannot mistake the vertically stacked operands for a single execution path or merge distinct returns.

Exact examples and bounds

Scenario Expected display and semantic boundary
User sequence C1,C2,R2,R1 L2 C1 starts at receive(C1), ends at send(R1); C2 starts at receive(C2), ends at send(R2), shifted one column. Both bars and both terminators remain visible and independently selectable. Two ordinary calls on L1 do not create an invented L1 execution.
Sequential C1,R1,C2,R2 Both L2 bars may use base column. No gratuitous staircase from counting all preceding calls.
Crossed C1,C2,R1,R2 Preserve exact correlation and both extents; overlapping bars use separate columns. This core-admitted model must not be rejected or changed into C1,C2,R2,R1. Do not call it proof of a single nested thread.
Self-call on inactive L1 The routed loop leaves the lane and returns to the new bar after its vertical drop. Bar top equals final receiving endpoint y, not the upper send row. An inactive caller has no fabricated execution before receipt.
A→B C1; B→B C2; self R2; B→A R1 C2 offsets from C1 on B. C2 call starts at the active parent edge and ends at child start; R2 starts at child end and returns to the still-active parent. Parent remains visible outside the child.
A→B C1; B→A C2; A→B R2; B→A R1 Bars belong to B for C1 and A for C2. Depth is per lifeline. Merely having an outgoing Call does not establish an A parent execution. If A was already active from an earlier received call, C2 is offset on A.
Incoming calls to same lifeline from different senders Use exact receiver and interval overlap, not exchange identity or sender equality. No thread relationship may be inferred.
ALT inherited outer call with Reply in one operand and no Reply in another Preserve segmented outer bar, exact matched end in returned operand, explicit open mark in unresolved operand, and existing post-fragment continuation rule. No bar across branch headers or false unconditional return.
PAR sibling local calls Render each in its own operand and correlate locally. Do not increase second sibling depth just because its row is later. Keep inherited outer-call behavior separately covered.
Missing Reply Preserve an explicit incomplete/open indicator; do not fabricate a reply or a full known execution duration. Current isolated open-call stub is 26 pixels. A stub cannot be used as evidence that activity definitely ended there. If it overlaps another displayed stub, separate them. Unknown semantic duration is not proof of nesting at a later call.
Reply without reply_to Remains a draft diagnostic and ordinary Reply line; it closes no bar.
Missing sender/receiver Preserve editable draft fallback. No invented lane or anchor. A Call with known receiver but missing sender may retain a clearly incomplete receiver activation, following current draft behavior.

Anchor rules: for ordinary inter-lifeline calls/replies, connect to the side of the relevant execution facing the other endpoint; a Reply's source execution is its exact correlated Call. Its target can attach to an unambiguously active caller execution; absent one, attach to the caller lane. When several independent active executions could be the caller, do not guess a semantic parent: use the lane or an explicitly documented neutral rule. For self-messages, route the loop outside the outermost involved bar and attach at parent/child edges; never route a loop through the bar. Compute label bounds from final route, not stale lane centers.

Candidate comparison

  1. Paint the selected bar last / change color only: small but fails visibility without selection and cannot distinguish same-x shapes. Reject.
  2. Count preceding Calls per lane: small but staircases sequential pairs, depends on unresolved calls, and falsely nests branch siblings. Reject.
  3. Single global active stack: apparently matches common examples but imposes unmodelled thread and LIFO assumptions, and mishandles branches/crossed correlations. Reject.
  4. Typed correlation segments plus deterministic path-aware overlap placement and shared anchors: modest renderer refactor, preserves existing source/REST semantics, and admits concrete paint/hit evaluators. Recommended.
  5. New explicit execution/thread metamodel: potentially useful later but not justified solely by this display defect; larger source/API/transition burden and more uncertainty before the reported scenario works.

Required evaluator (not executed)

Start with actual core-built Project fixtures, validation and canonical source round trips, then pass those accepted Projects to the production Scene. Do not use a miniature fake project or a reimplementation of activation logic as the sole oracle. .local/nested-call-fixtures-20260912.rs supplies deterministic actual-core fixture construction for a first gate; root must compile/run it, inspect warnings and add the resulting paths to native verification. The probe intentionally uses missing exchange references to isolate activation semantics in both layers; native release acceptance should also repeat the user case with an actual bound exchange and values.

For C1,C2,R2,R1, inspect final production activation records: top/end match actual line receive/send vertices, width positive, child x differs, exposed horizontal interval of each is nonempty, and neither terminal is swallowed by another fill. Permute UUIDs while keeping steps/correlation identical; geometry and paint order must be equivalent. Inspect selected and unselected cases, both lane directions, self-call drop, crossed returns, 3+ nesting levels and two different receiver lanes.

Call Scene::hit on an exposed inner shoulder away from message lines and labels, and on an exposed outer shoulder. Expect the respective Call IDs at default/minimum scale and nonzero camera pan. Test each matching Reply line separately. Activation body clicks must not select the lane or unrelated outer Call. Account for actual 8/scale line hit tolerances: mere bar-center checks near message rows can pass by hitting a line instead of the bar.

Paint order gate: backgrounds/fragments, lifeline guides, activation bars shallow→deep, message routes/arrowheads, labels/selection adorners as appropriate. Lifeline guides must not appear through filled activation interiors; messages must end at intended bar edges. Generic rounded-card rendering is inappropriate for narrow execution rectangles.

Use actual Iced draw output/native screenshot at default/minimum scale in 1100×700 and normal larger window. Verify exposed shoulders, starts/ends, self loops, line dashes, unselected contrast and selected outer/inner strokes. A Scene coordinate test does not prove paint layering. Exercise selecting each bar and each reply, editing reply correlation, changing order, Undo/Redo and save/reopen. Canonical source and the full accepted model must retain IDs/steps/endpoints/values; merely selecting/opening never dirties the model. Reuse existing valid lifecycle/header/ALT gates and rerun affected ones.

This bounded repair cannot satisfy issue 20/22's free arrangement/editing requirements or the overall Behavior UX objective. It does not deliver persisted lane placement, arbitrary visual spacing, process geometry, cross-operand movement or a general execution metamodel. Root should record which slice is integrated without closing broader work.