fix(central-ui): Flow Debugger spawn edges — real arrowhead + bolt badge; spawner picked by trigger attribute
The trigger-cascade (Spawn) edge used a lightning-bolt marker IN PLACE of the arrowhead. On the upward Tags→Site hop the bolt sat on top of the riser and read as a broken arrow (rig screenshot 2026-09-08). Every connector now ends in an ordinary arrowhead; the bolt is a badge (circle + bolt) centred on the connector's vertical run (FlowGraphView.SpawnBadgePoint, kept in step with OrthogonalPath/AsyncPath). Same screenshot, second defect: the cascade spawned by "RecipeDownloadFlag = false" was drawn from the LAST write of the burst (RecipeProcessedFlag) because all four writes share a millisecond and the spawner rule was time-only. FlowGraphBuilder now prefers the parent's last AttributeWrite of the attribute the child's triggerAttribute names (at or before the child, else the last such write); the time/position rules remain the fallback when the child names no attribute or the parent never wrote it. Verified live on the rebuilt rig (DelmiaSim01 / DelmiaReceiverSimulator): both spawn edges show an arrowhead + badge, the second one originates from the RecipeDownloadFlag write. bUnit + builder tests updated/added; Playwright FlowDebuggerTests 4/4. Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -472,6 +472,47 @@ public class FlowGraphBuilderTests
|
||||
Assert.Equal(cascade.EventId, spawn.ToEventId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimultaneousWrites_TriggerCascade_SpawnsFromTheWriteOfTheTriggerAttribute_NotTheLastWrite()
|
||||
{
|
||||
// Rig 2026-09-08: a run wrote Result, ResultText, Flag=false and Processed inside the same millisecond,
|
||||
// and the value-change child (trigger attribute Flag) was drawn spawning from Processed — the last write
|
||||
// at or before the child. The child names its trigger attribute, so that write wins outright.
|
||||
var run = SiteRunRow(0);
|
||||
var result = WriteRow(10, "DelmiaReceiver.Result");
|
||||
var flag = WriteRow(10, "DelmiaReceiver.Flag");
|
||||
var processed = WriteRow(10, "DelmiaReceiver.Processed");
|
||||
var cascade = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
||||
occurredAtUtc: T0.AddMilliseconds(10), actor: "OnFlag", target: "OnFlag", sourceNode: "node-a",
|
||||
executionId: Cascade, parentExecutionId: SiteRun, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 3,
|
||||
extra: new ScriptTraceExtra { TriggerKind = "valuechange", TriggerAttribute = "delmiareceiver.flag" }.ToJson());
|
||||
|
||||
var graph = FlowGraphBuilder.Build([run, result, flag, processed, cascade]);
|
||||
|
||||
var spawn = Assert.Single(graph.Edges, e => e.Spawn);
|
||||
Assert.Equal(flag.EventId, spawn.FromEventId);
|
||||
Assert.Equal(cascade.EventId, spawn.ToEventId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TriggerCascade_WhoseAttributeTheParentNeverWrote_FallsBackToTheTimeRule()
|
||||
{
|
||||
// The device echo of a DCL-sourced change, or a trace from an older site that names an attribute
|
||||
// the parent chain has no write for: the attribute rule finds nothing and the time rule still applies.
|
||||
var run = SiteRunRow(0);
|
||||
var writeA = WriteRow(10, "DelmiaReceiver.A");
|
||||
var writeB = WriteRow(30, "DelmiaReceiver.B");
|
||||
var cascade = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
||||
occurredAtUtc: T0.AddMilliseconds(15), actor: "OnX", target: "OnX", sourceNode: "node-a",
|
||||
executionId: Cascade, parentExecutionId: SiteRun, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 3,
|
||||
extra: new ScriptTraceExtra { TriggerKind = "valuechange", TriggerAttribute = "DelmiaReceiver.X" }.ToJson());
|
||||
|
||||
var graph = FlowGraphBuilder.Build([run, writeA, writeB, cascade]);
|
||||
|
||||
var spawn = Assert.Single(graph.Edges, e => e.Spawn);
|
||||
Assert.Equal(writeA.EventId, spawn.FromEventId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoldedNode_TakesTheEmissionIndexOfItsEarliestEmittedRow()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user