859922ba17
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
134 lines
6.2 KiB
CSS
134 lines
6.2 KiB
CSS
/* Flow Debugger swimlane — mirrors the design mockup's values through the kit's
|
|
CSS variables (theme.css light defaults; site.css dark overrides) so dark mode
|
|
follows automatically. Border #d2d2cb = --rule-strong, selected 2px #2f5fd0 on
|
|
#eef2fc = --accent on --active-bg. */
|
|
|
|
.fg-wrap {
|
|
overflow: auto;
|
|
border: 1px solid var(--rule);
|
|
border-radius: 4px;
|
|
background: var(--bs-body-bg, #fff);
|
|
}
|
|
|
|
.fg-svg {
|
|
display: block;
|
|
font-family: var(--bs-body-font-family, system-ui, sans-serif);
|
|
}
|
|
|
|
/* Lanes. The gutter labels are emitted as raw markup (SvgText — Razor reserves the <text> tag),
|
|
so they carry no CSS-isolation scope attribute and must be reached through ::deep. */
|
|
.fg-lane-bg { fill: transparent; }
|
|
.fg-lane-bg.alt { fill: var(--rule); fill-opacity: 0.25; }
|
|
.fg-lane-rule { stroke: var(--rule); stroke-width: 1; }
|
|
.fg-wrap ::deep .fg-lane-label { fill: var(--ink-soft); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
.fg-wrap ::deep .fg-lane-sublabel{ fill: var(--ink-faint); font-size: 0.65rem; font-family: var(--mono); }
|
|
.fg-wrap ::deep .fg-lane-prefix { fill: var(--ink-faint); font-size: 0.65rem; font-family: var(--mono); }
|
|
.fg-wrap ::deep .fg-empty { fill: var(--ink-faint); font-size: 0.85rem; font-style: italic; }
|
|
|
|
/* Nodes */
|
|
.fg-node { cursor: pointer; outline: none; }
|
|
.fg-node .fg-node-box {
|
|
fill: var(--bs-body-bg, #fff);
|
|
stroke: var(--rule-strong);
|
|
stroke-width: 1;
|
|
}
|
|
.fg-node:hover .fg-node-box,
|
|
.fg-node:focus-visible .fg-node-box { stroke: var(--accent); }
|
|
.fg-node.selected .fg-node-box {
|
|
stroke: var(--accent);
|
|
stroke-width: 2;
|
|
fill: var(--active-bg);
|
|
}
|
|
.fg-node.predicted .fg-node-box { stroke-dasharray: 4 3; }
|
|
.fg-node.input-dependent .fg-node-box { stroke-dasharray: 2 3; }
|
|
.fg-node.ghost { opacity: 0.45; }
|
|
.fg-node.ghost .fg-node-box { fill: var(--rule); fill-opacity: 0.35; }
|
|
.fg-node.failed .fg-node-box,
|
|
.fg-node.timed-out .fg-node-box { stroke: var(--bs-danger, #c0392b); }
|
|
.fg-node.not-traced .fg-node-box { stroke-dasharray: 1 3; }
|
|
.fg-node.parked .fg-node-box { stroke: var(--warn, var(--bs-warning, #e8920c)); }
|
|
|
|
/* Node body: HTML inside a foreignObject so text is measured by the browser. */
|
|
.fg-body {
|
|
box-sizing: border-box;
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 6px 8px 5px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
overflow: hidden;
|
|
line-height: 1.2;
|
|
color: var(--ink);
|
|
font-family: var(--bs-body-font-family, system-ui, sans-serif);
|
|
}
|
|
.fg-head { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; }
|
|
.fg-eyebrow { color: var(--ink-faint); font-size: 0.6rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.fg-duration { color: var(--ink-soft); font-size: 0.6rem; font-family: var(--mono); white-space: nowrap; flex-shrink: 0; }
|
|
.fg-title {
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
overflow-wrap: anywhere;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
.fg-subtitle { color: var(--ink-soft); font-size: 0.66rem; margin-top: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.fg-node.failed .fg-eyebrow,
|
|
.fg-node.timed-out .fg-eyebrow { color: var(--bs-danger, #c0392b); }
|
|
|
|
/* Outbound nodes (API call / database / notify): the folded outcome as a status pill, then the
|
|
call mode. Kit status tokens first (dark mode follows via site.css), Bootstrap's as fallback. */
|
|
.fg-status { margin-top: auto; display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
.fg-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
font-size: 0.6rem;
|
|
font-weight: 500;
|
|
line-height: 1.3;
|
|
padding: 0 6px;
|
|
border-radius: 999px;
|
|
white-space: nowrap;
|
|
color: var(--ink-soft);
|
|
background: var(--rule);
|
|
}
|
|
.fg-pill::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
|
|
.fg-pill.ok { color: var(--ok, var(--bs-success, #2f9e44)); background: var(--ok-bg, rgba(var(--bs-success-rgb, 25, 135, 84), 0.12)); }
|
|
.fg-pill.pending { color: var(--warn, var(--bs-warning, #e8920c)); background: var(--warn-bg, rgba(var(--bs-warning-rgb, 255, 193, 7), 0.15)); }
|
|
.fg-pill.parked { color: var(--warn, var(--bs-warning, #e8920c)); background: var(--warn-bg, rgba(var(--bs-warning-rgb, 255, 193, 7), 0.15)); }
|
|
.fg-pill.failed { color: var(--bad, var(--bs-danger, #e03131)); background: var(--bad-bg, rgba(var(--bs-danger-rgb, 220, 53, 69), 0.12)); }
|
|
.fg-mode { color: var(--ink-soft); font-size: 0.66rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
/* A pending pill pulses while the page keeps refreshing outcomes after a Live run. */
|
|
.fg-node.pending .fg-pill { animation: fg-pulse 1.6s ease-in-out infinite; }
|
|
@keyframes fg-pulse {
|
|
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 var(--warn-bg, rgba(var(--bs-warning-rgb, 255, 193, 7), 0.15)); }
|
|
50% { opacity: 0.6; box-shadow: 0 0 0 4px var(--warn-bg, rgba(var(--bs-warning-rgb, 255, 193, 7), 0.15)); }
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.fg-node.pending .fg-pill { animation: none; }
|
|
}
|
|
|
|
/* Connectors */
|
|
.fg-edge {
|
|
fill: none;
|
|
stroke: var(--ink-faint);
|
|
stroke-width: 1.5;
|
|
}
|
|
.fg-edge.boundary { stroke: var(--accent); }
|
|
.fg-edge.external { stroke-dasharray: 5 4; }
|
|
/* Async branch (cached call / notification): same dash as the echo edge, its own class — the run
|
|
carried on without waiting. Spawn: solid line with an ordinary arrowhead plus a bolt badge sitting
|
|
on the riser (a trigger cascade) — the bolt is NOT the arrowhead, that read as a broken arrow. */
|
|
.fg-edge.async { stroke-dasharray: 5 4; }
|
|
.fg-edge.spawn { stroke: var(--ink-soft); }
|
|
.fg-edge.predicted { stroke-opacity: 0.5; stroke-dasharray: 3 3; }
|
|
.fg-arrowhead { fill: var(--ink-faint); }
|
|
.fg-arrowhead.accent { fill: var(--accent); }
|
|
.fg-spawn-badge-bg { fill: var(--bs-body-bg, #fff); stroke: var(--ink-soft); stroke-width: 1; }
|
|
.fg-spawn-bolt { fill: var(--ink-soft); }
|
|
.fg-wrap ::deep .fg-edge-tag { fill: var(--ink-faint); font-size: 0.6rem; font-family: var(--mono); }
|