CanvasActionRunner
Runs one canvas's <c>action</c> bindings. Owned by a <c>DrylCanvas</c> instance, like CanvasDataBinder — two canvases on a page share nothing. <para>Its only entry point is <see cref="M:DRYL.Components.Canvas.CanvasActionRunner.InvokeAsync(System.String,System.String,DRYL.Components.Canvas.CanvasActionBinding)" />, and its only caller is a rendered button's click handler. There is deliberately no path from a model output to here: the AI builds and labels the button, the human presses it.</para><para>A completed action is one movement: patch ops land in one batch and pulse, the named sources reload through the existing binder, a success message is a toast, and a failure stays inline at the button.</para><para> <b>Never <c>ConfigureAwait(false)</c> in here.</b> The whole sequence starts on a click and ends by touching components — the toast provider, the host's <c>OnAction</c>, the canvas's own render. Dropping the Blazor dispatcher at any await means the continuation calls <c>StateHasChanged</c> off-thread and kills the circuit. (<see cref="T:DRYL.Components.Canvas.CanvasDataBinder" /> may do it because every signal it raises is marshalled back through <c>InvokeAsync</c>; this runner talks to components directly.)</para>