Follow-up to #3610522. The instance trace now offers an "Open" link on a parked human node (its operator surface). After the operator processes that task, they are returned to the surface's own default (the inbox for a user task, the pending-actions list for an operation), not back to the trace they opened it from.
For an operator driving an instance from the trace (an admin debugging, or a support agent), returning to the trace would keep them in context.
Idea
Thread a return target through the "Open" URL the trace builds (a destination or a per-handler return query), so completion redirects back to the trace. Some completion surfaces already honor a return (the interaction task reads orchestra_return), but the operation and inbox forms redirect to their own fixed landing, so this needs a per-handler look, not a single switch.
Low priority: a convenience, not a correctness issue.
Issue fork orchestra-3610524
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mably commentedDesign notes for a future implementation.
The tempting one-liner, adding ?destination= to the trace "Open" link, is not safe. Core's RedirectResponseSubscriber overrides any redirect with destination, including the open-time redirect some interactions perform: a webform-collect step or a payment step redirects the operator off to the form or PSP on open, so with destination set that redirect is hijacked back to the trace and the operator never reaches the task. It only works for surfaces that render in place (an order_review).
A correct fix must return only after completion, not on the open-time redirect, and the completion surfaces each handle return differently:
So the implementation should thread a "return after completion" target through each completion path (orchestra_interaction_task, orchestra_ui operations, orchestra_inbox), rather than a blanket destination. Worth a functional test driving open-from-trace to complete to land-on-trace.
Comment #3
mably commentedDesign for the record. Two layers: a stateless return parameter for the direct completion surfaces, and a durable server-side entry for the collect round-trips (webform edit/collect and payment), where a query parameter cannot survive the hop.
Shared helper
A single
OrchestraReturnhelper in the base module owns the convention: anorchestra_returnquery argument, an internal-only guard (UrlHelper::isExternal), and a LocalRedirectResponse, never a destination parameter (which RedirectResponseSubscriber would override). It replaces the two divergent copies that already exist: InteractionTaskController::signal (orchestra_return, falls back to the inbox) and CommentForm (its RETURN_ARG "return", falls back to the front page). Both standardize on orchestra_return.Direct surfaces
The trace is built by OrchestraUiController (the instance canonical page), which resolves each parked token's Open URL through PendingActionsFinder::actUrlsFor(). Thread an optional return argument through actUrlFor/actUrlsFor, PendingActionHandlerInterface::actUrl/actUrls, and the three handlers (OperationActionHandler, InboxTaskHandler, InteractionTaskActionHandler), each calling OrchestraReturn::embed(), so every Open link carries the current trace URL. Honor it at completion, each surface keeping its current landing as the fallback:
The interface change is internal to Orchestra (no external consumers).
Collect round-trip (webform edit/collect and payment)
A stateless parameter cannot survive here: InteractionToken is a minimal signed capability with no payload room, ReturnsToWorkflowTrait::returnToWorkflow() deliberately reads nothing from the request, and the dispatcher terminal renders a message rather than redirecting. So the return is stored server-side, keyed by the opened token and the operator:
Payment
Payment is best-effort. Settlement can be asynchronous and server-side while the browser returns separately to the dispatcher, so the opened token may already be consumed when the operator's browser lands. It likely honors on the browser return, keyed by the branch token carried in the return capability and guarded by uid. It degrades gracefully to the dispatcher message, and uid-keying structurally prevents a real payer from ever being bounced to an operator trace.
Not this issue
InteractionContext::returnUrl() and branchReturnUrl() are the dispatcher continuation layer (token-scoped capability URLs that chain to the next interaction); they are left untouched.
Boundaries
Tests
Functional per surface: open a parked task from the trace, complete it, assert the redirect lands back on the trace rather than the default landing; plus the webform collect case with return_to_workflow on and off. Kernel coverage for OrchestraReturn (embed coexists with an existing query, resolve honors internal, rejects external, empty falls back) and the durable trio (remember, recall, forget round-trip, TTL, per token-and-uid isolation).
Comment #5
mably commentedComment #7
mably commented