Files
ScadaBridge/docs/plans/2026-09-09-flow-debugger-step-logs-mockup.html
T

358 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<title>Flow Debugger Step Logs</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
<style>
:root {
--ground: #f4f3ee; --surface: #fbfaf7; --surface-2: #f0efe9;
--ink: #1f231f; --ink-soft: #5d625d; --ink-faint: #8a8f89;
--rule: #dcdbd3; --rule-strong: #c7c6bd;
--accent: #2f5fd0; --active-bg: #eef2fc;
--ok: #2e7d4a; --ok-bg: #e4f2e8; --warn: #b8741a; --warn-bg: #fbf0dc; --danger: #b3362d; --danger-bg: #f9e3e0;
--sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--ground: #1b1d1b; --surface: #242724; --surface-2: #2c2f2c;
--ink: #e8e8e1; --ink-soft: #b3b6b0; --ink-faint: #7f837e;
--rule: #383b37; --rule-strong: #4a4e49;
--accent: #7f9ff0; --active-bg: #23304d;
--ok: #7dcb95; --ok-bg: #1f3a2a; --warn: #e2b264; --warn-bg: #3f3117; --danger: #ee8b82; --danger-bg: #46231f;
}
}
:root[data-theme="dark"] {
--ground: #1b1d1b; --surface: #242724; --surface-2: #2c2f2c;
--ink: #e8e8e1; --ink-soft: #b3b6b0; --ink-faint: #7f837e;
--rule: #383b37; --rule-strong: #4a4e49;
--accent: #7f9ff0; --active-bg: #23304d;
--ok: #7dcb95; --ok-bg: #1f3a2a; --warn: #e2b264; --warn-bg: #3f3117; --danger: #ee8b82; --danger-bg: #46231f;
}
body { background: var(--ground); color: var(--ink); font-family: var(--sans); font-size: 14px; line-height: 1.45; }
.page { max-width: 1180px; margin: 0 auto; padding: 28px 24px 56px; display: flex; flex-direction: column; gap: 26px; }
h1 { font-size: 1.45rem; font-weight: 600; margin: 0; letter-spacing: -0.01em; text-wrap: balance; }
h2 { font-size: 0.95rem; font-weight: 600; margin: 0 0 8px; }
p { margin: 0; max-width: 68ch; }
.lede { color: var(--ink-soft); }
.eyebrow { font-size: 0.68rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
a { color: var(--accent); }
code { font-family: var(--mono); font-size: 0.86em; background: var(--surface-2); padding: 0 4px; border-radius: 3px; }
/* ── the mocked screen ─────────────────────────────────────────────── */
.screen { background: var(--surface); border: 1px solid var(--rule); border-radius: 6px; padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 12px; }
.toolbar { display: flex; flex-wrap: wrap; gap: 14px 22px; align-items: flex-end; }
.field label { display: block; font-size: 0.72rem; color: var(--ink-soft); margin-bottom: 3px; }
.seg { display: inline-flex; border: 1px solid var(--rule-strong); border-radius: 4px; overflow: hidden; }
.seg span { padding: 4px 10px; font-size: 0.8rem; color: var(--ink-soft); background: var(--surface); }
.seg span.on { background: var(--accent); color: #fff; }
.select { border: 1px solid var(--rule-strong); border-radius: 4px; padding: 4px 28px 4px 8px; font-size: 0.8rem; background: var(--surface) linear-gradient(45deg, transparent 50%, var(--ink-faint) 50%) no-repeat right 12px top 55% / 6px 6px; min-width: 190px; }
.btn { border: 1px solid var(--rule-strong); border-radius: 4px; padding: 4px 12px; font-size: 0.8rem; background: var(--surface); color: var(--ink); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.sm { padding: 1px 8px; font-size: 0.74rem; }
.spacer { flex: 1; }
.status { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 4px; background: var(--surface-2); font-size: 0.8rem; }
.status .k { color: var(--ink-faint); font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; margin-right: 6px; }
.chip { border: 1px solid var(--ok); color: var(--ok); border-radius: 999px; padding: 1px 10px; font-size: 0.76rem; font-weight: 500; }
.chip.failed { border-color: var(--danger); color: var(--danger); }
.new { outline: 2px dashed var(--accent); outline-offset: 3px; border-radius: 3px; }
.cnt.err { color: var(--danger); font-weight: 600; }
.cnt.warn { color: var(--warn); font-weight: 600; }
.flow-wrap { border: 1px solid var(--rule); border-radius: 4px; background: var(--surface); overflow-x: auto; }
svg.fg { display: block; width: 1500px; height: 456px; font-family: var(--sans); }
.lane-bg.alt { fill: var(--rule); fill-opacity: .25; } .lane-bg { fill: transparent; }
.lane-rule { stroke: var(--rule); stroke-width: 1; }
.lane-label { fill: var(--ink-soft); font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
.lane-sub { fill: var(--ink-faint); font-size: 10.5px; font-family: var(--mono); }
.fg-node { cursor: pointer; }
.fg-node rect { fill: var(--surface); stroke: var(--rule-strong); stroke-width: 1; }
.fg-node.selected rect { stroke: var(--accent); stroke-width: 2; fill: var(--active-bg); }
.fg-node.failed rect { stroke: var(--danger); }
.fg-node.failed .eb { color: var(--danger); }
.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(--sans); }
.head { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; }
.eb { color: var(--ink-faint); font-size: 9.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dur { color: var(--ink-soft); font-size: 9.5px; font-family: var(--mono); white-space: nowrap; }
.title { font-size: 12px; font-weight: 600; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.sub { color: var(--ink-soft); font-size: 10.5px; margin-top: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sub.exc { color: var(--danger); font-family: var(--mono); }
.foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 4px; min-width: 0; }
.foot .sub { margin-top: 0; flex: 1; }
/* the new log badge: highest severity first, counts per level, tabular */
.lg { display: inline-flex; gap: 3px; flex-shrink: 0; font-family: var(--mono); font-size: 9.5px; font-variant-numeric: tabular-nums; }
.lg i { font-style: normal; display: inline-flex; align-items: center; gap: 2px; padding: 0 4px; border-radius: 3px; line-height: 1.35; }
.lg .e { color: var(--danger); background: var(--danger-bg); }
.lg .w { color: var(--warn); background: var(--warn-bg); }
.lg .i { color: var(--ink-faint); background: var(--surface-2); }
.pill { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; font-size: 10px; font-weight: 500; padding: 1px 7px; border-radius: 999px; white-space: nowrap; }
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill.ok { color: var(--ok); background: var(--ok-bg); }
.mode { color: var(--ink-soft); font-size: 10.5px; }
.edge { fill: none; stroke: var(--ink-faint); stroke-width: 1.5; }
.edge.accent { stroke: var(--accent); }
.edge.dashed { stroke-dasharray: 4 3; }
.arrow { fill: var(--ink-faint); } .arrow.accent { fill: var(--accent); }
/* detail panel */
.detail { border: 1px solid var(--rule); border-radius: 4px; background: var(--surface); padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 10px; }
.dhead { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.dhead h3 { margin: 2px 0 0; font-size: 1rem; font-weight: 600; }
.dhead .subl { font-size: 0.78rem; color: var(--ink-soft); }
.badge { font-size: 0.72rem; font-weight: 600; padding: 2px 8px; border-radius: 3px; background: var(--danger); color: #fff; }
.cols { display: grid; grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr); gap: 18px; }
@media (max-width: 760px) { .cols { grid-template-columns: 1fr; } }
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--rule); }
.tabs span { padding: 5px 11px; font-size: 0.8rem; color: var(--ink-soft); border: 1px solid transparent; border-bottom: none; border-radius: 4px 4px 0 0; margin-bottom: -1px; }
.tabs span.on { color: var(--ink); background: var(--surface); border-color: var(--rule); border-bottom-color: var(--surface); font-weight: 500; }
.tabs .n { color: var(--ink-faint); margin-left: 4px; font-family: var(--mono); font-size: 0.72rem; }
.pane { padding: 12px 2px 0; display: flex; flex-direction: column; gap: 10px; }
.exc { border: 1px solid var(--danger); border-left-width: 4px; border-radius: 4px; background: var(--danger-bg); padding: 8px 12px; display: flex; flex-direction: column; gap: 4px; }
.exc .t { font-family: var(--mono); font-size: 0.8rem; font-weight: 600; color: var(--danger); }
.exc .m { font-size: 0.86rem; }
.exc pre { margin: 6px 0 0; font-family: var(--mono); font-size: 0.72rem; line-height: 1.45; color: var(--ink-soft); white-space: pre; overflow-x: auto; }
.exc .row { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
.exc .row .btn { background: var(--surface); }
.logctl { display: flex; align-items: center; gap: 12px; font-size: 0.76rem; color: var(--ink-soft); }
.logctl .seg span { padding: 2px 8px; font-size: 0.74rem; }
table.log { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
table.log th { text-align: left; font-size: 0.66rem; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); font-weight: 600; padding: 4px 8px 6px; border-bottom: 1px solid var(--rule); }
table.log td { padding: 5px 8px; border-bottom: 1px solid var(--rule); vertical-align: top; }
table.log tr:last-child td { border-bottom: none; }
td.t { font-family: var(--mono); font-size: 0.74rem; color: var(--ink-soft); white-space: nowrap; }
td.src { color: var(--ink-faint); font-size: 0.74rem; white-space: nowrap; }
.lvl { display: inline-block; min-width: 46px; text-align: center; font-size: 0.66rem; font-weight: 600; letter-spacing: .04em; padding: 1px 6px; border-radius: 3px; }
.lvl.info { color: var(--ink-soft); background: var(--surface-2); }
.lvl.warn { color: var(--warn); background: var(--warn-bg); }
.lvl.error { color: var(--danger); background: var(--danger-bg); }
.lvl.debug { color: var(--ink-faint); background: transparent; border: 1px dashed var(--rule-strong); }
tr.dim td { color: var(--ink-faint); font-style: italic; }
.where dl { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; margin: 0; font-size: 0.8rem; }
.where dt { color: var(--ink-soft); } .where dd { margin: 0; font-family: var(--mono); font-size: 0.76rem; overflow-wrap: anywhere; }
.where .acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
/* anatomy + author sections */
.grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 16px; }
.cap { font-size: 0.78rem; color: var(--ink-soft); margin-top: 8px; max-width: none; }
.nodebox { width: 150px; height: 84px; border: 1px solid var(--rule-strong); border-radius: 4px; background: var(--surface); }
.nodebox.failed { border-color: var(--danger); }
.nodebox.failed .eb { color: var(--danger); }
pre.code { margin: 0; background: var(--surface); border: 1px solid var(--rule); border-radius: 4px; padding: 12px 14px; font-family: var(--mono); font-size: 0.76rem; line-height: 1.5; overflow-x: auto; }
pre.code .c { color: var(--ink-faint); } pre.code .k { color: var(--accent); } pre.code .s { color: var(--ok); }
table.plain { border-collapse: collapse; font-size: 0.8rem; width: 100%; }
table.plain th, table.plain td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--rule); vertical-align: top; }
table.plain th { font-size: 0.66rem; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); }
.tbl-wrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: 4px; background: var(--surface); }
ol.notes { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; max-width: 78ch; }
ol.notes li { padding-left: 4px; }
ol.notes b { font-weight: 600; }
</style>
<div class="page">
<header>
<div class="eyebrow">Flow Debugger · v1.4 proposal · 2026-09-09</div>
<h1>Exceptions and log lines on the step that produced them</h1>
<p class="lede" style="margin-top:6px">A traced run carries every <code>Log.*</code> line a script writes, every warning the runtime logs on the script's behalf, and the full exception a failing step threw. Each lands on its own node: a count badge on the node, a <b>Log</b> tab in the detail panel, and run-wide error and warning counts in the status strip. Live and Replay read the same rows.</p>
</header>
<!-- ───────────────────────────── the screen ───────────────────────────── -->
<section class="screen" aria-label="Flow Debugger after a failed Live run">
<div class="toolbar">
<div class="field"><label>Entry point</label><div class="seg"><span class="on">Inbound API method</span><span>Site script</span></div></div>
<div class="field"><label>Method</label><div class="select">DelmiaRecipeDownload</div></div>
<div class="field"><label>Mode</label><div class="seg"><span class="on">Live</span><span>Replay</span></div></div>
<div class="spacer"></div>
<span class="btn">Edit inputs</span><span class="btn primary">Run</span>
</div>
<div class="status">
<span class="chip failed">Failed</span>
<span><span class="k">Run</span><span class="mono">#7</span></span>
<span><span class="k">Started</span><span class="mono">14:02:11.198Z</span></span>
<span><span class="k">Total</span><span class="mono">1 842 ms</span></span>
<span><span class="k">Steps</span><span class="mono">8</span></span>
<span><span class="k">Boundaries</span><span class="mono">2</span></span>
<span><span class="k">Writes</span><span class="mono">2</span></span>
<span class="new"><span class="k">Log</span><span class="mono"><span class="cnt err">1 error</span> · <span class="cnt warn">2 warnings</span> · 4 info</span></span>
<span class="btn sm">Refresh outcomes</span>
<span><span class="k">Root</span><a class="mono" href="#">7c1e0f2a-…-9b41</a></span>
</div>
<div class="flow-wrap">
<svg class="fg" viewBox="0 0 1500 456" role="img" aria-label="Swimlane: request, failed route call, failed site script with log badge, two writes, a satisfied wait, a database call, and a 500 response">
<defs>
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" class="arrow"/></marker>
<marker id="ar-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" class="arrow accent"/></marker>
</defs>
<!-- lanes -->
<rect x="0" y="0" width="1500" height="110" class="lane-bg"/>
<rect x="0" y="110" width="1500" height="110" class="lane-bg alt"/>
<rect x="0" y="220" width="1500" height="110" class="lane-bg"/>
<rect x="0" y="330" width="1500" height="110" class="lane-bg alt"/>
<line x1="0" y1="110" x2="1500" y2="110" class="lane-rule"/><line x1="0" y1="220" x2="1500" y2="220" class="lane-rule"/><line x1="0" y1="330" x2="1500" y2="330" class="lane-rule"/>
<text x="12" y="24" class="lane-label">Central</text><text x="12" y="40" class="lane-sub">central-a</text>
<text x="12" y="134" class="lane-label">Site</text><text x="12" y="150" class="lane-sub">site-a · DelmiaSim01</text>
<text x="12" y="244" class="lane-label">Tags</text><text x="12" y="260" class="lane-sub">DelmiaReceiver.*</text>
<text x="12" y="354" class="lane-label">External</text><text x="12" y="370" class="lane-sub">MesAudit</text>
<!-- edges (columns at x = 140 + 170·n, node 150×84, lane centre y = lane·110 + 55) -->
<path d="M290 55 H310" class="edge accent" marker-end="url(#ar-a)"/>
<path d="M460 55 H470 V165 H480" class="edge accent" marker-end="url(#ar-a)"/>
<path d="M630 165 H640 V275 H650" class="edge" marker-end="url(#ar)"/>
<path d="M800 275 H820" class="edge" marker-end="url(#ar)"/>
<path d="M970 275 H980 V165 H990" class="edge" marker-end="url(#ar)"/>
<path d="M895 233 V190 H990" class="edge dashed" marker-end="url(#ar)"/>
<path d="M1140 165 H1150 V385 H1160" class="edge" marker-end="url(#ar)"/>
<path d="M460 55 H1330" class="edge accent" marker-end="url(#ar-a)"/>
<!-- nodes -->
<g class="fg-node" transform="translate(140 13)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Inbound</span><span class="dur">1 842 ms</span></div><div class="title">DelmiaRecipeDownload</div><div class="foot"><span class="sub">flow-debugger · jdoherty</span><span class="lg"><i class="i">≡ 1</i></span></div></div></foreignObject></g>
<g class="fg-node failed" transform="translate(310 13)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Route call</span><span class="dur">1 790 ms</span></div><div class="title">DelmiaSim01.ProcessRecipeDownload</div><div class="foot"><span class="sub exc">Script failed at site</span></div></div></foreignObject></g>
<g class="fg-node failed selected" transform="translate(480 123)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Script run</span><span class="dur">1 731 ms</span></div><div class="title">ProcessRecipeDownload</div><div class="foot"><span class="sub exc">InvalidOperationException: Recipe 42 rejected by Galaxy (code 7)</span><span class="lg"><i class="e">✕ 1</i><i class="w">! 1</i><i class="i">≡ 2</i></span></div></div></foreignObject></g>
<g class="fg-node" transform="translate(650 233)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Write</span><span class="dur">12 ms</span></div><div class="title">RecipeId, RecipeName</div><div class="foot"><span class="sub">RecipeId = 42, RecipeName = …</span><span class="lg"><i class="w">! 1</i></span></div></div></foreignObject></g>
<g class="fg-node" transform="translate(820 233)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Write</span><span class="dur">9 ms</span></div><div class="title">DownloadRequest</div><div class="foot"><span class="sub">DownloadRequest = true</span></div></div></foreignObject></g>
<g class="fg-node" transform="translate(990 123)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Wait</span><span class="dur">1 640 ms</span></div><div class="title">RecipeProcessedFlag</div><div class="foot"><span class="sub">satisfied · true</span></div></div></foreignObject></g>
<g class="fg-node" transform="translate(1160 343)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Database</span><span class="dur">23 ms</span></div><div class="title">MesAudit</div><div class="foot"><span class="pill ok">Ok</span><span class="mode">· write · 1 row</span><span class="lg"><i class="i">≡ 1</i></span></div></div></foreignObject></g>
<g class="fg-node failed" transform="translate(1330 13)"><rect width="150" height="84" rx="4"/><foreignObject width="150" height="84"><div xmlns="http://www.w3.org/1999/xhtml" class="body"><div class="head"><span class="eb">Response</span><span class="dur"></span></div><div class="title">Response</div><div class="foot"><span class="sub exc">500 · SCRIPT_ERROR</span></div></div></foreignObject></g>
</svg>
</div>
<!-- detail panel for the selected (failed) script run -->
<div class="detail" aria-label="Step detail: ProcessRecipeDownload, Log tab">
<div class="dhead">
<div>
<div class="eyebrow">Script run · Site</div>
<h3>ProcessRecipeDownload</h3>
<div class="subl">DelmiaSim01.DelmiaReceiver · call</div>
</div>
<span class="badge">Failed</span>
</div>
<div class="cols">
<div>
<div class="tabs"><span>Input</span><span>Output</span><span class="on new">Log<span class="n">5</span></span><span>Writes</span><span>Timing</span></div>
<div class="pane">
<div class="exc">
<div class="t">System.InvalidOperationException</div>
<div class="m">Recipe 42 rejected by Galaxy (code 7)</div>
<pre> at Submission#0.&lt;&lt;Initialize&gt;&gt;d__0.MoveNext() in ProcessRecipeDownload:line 41
at ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.ScriptRunLauncher.RunScriptAsync(…)
Inner exception: none</pre>
<div class="row"><span class="btn sm">Copy</span><span class="btn sm">Open in Audit Log</span><span style="font-size:.74rem;color:var(--ink-soft)">Thrown by the script body · site-a / node-a</span></div>
</div>
<div class="logctl">
<div class="seg"><span class="on">All</span><span>Warn +</span></div>
<span>5 lines on this step · <a href="#">1 more on Write RecipeId, RecipeName</a></span>
<span class="spacer"></span>
<span class="btn sm">Copy lines</span>
</div>
<table class="log">
<thead><tr><th>Time (UTC)</th><th>Level</th><th>Source</th><th>Message</th></tr></thead>
<tbody>
<tr><td class="t">14:02:11.204</td><td><span class="lvl info">info</span></td><td class="src">script</td><td>Recipe 42 requested for line L3 by flow-debugger</td></tr>
<tr><td class="t">14:02:11.206</td><td><span class="lvl debug">debug</span></td><td class="src">script</td><td>Writing header attributes then DownloadRequest</td></tr>
<tr><td class="t">14:02:12.861</td><td><span class="lvl info">info</span></td><td class="src">script</td><td>Galaxy answered: RecipeProcessedFlag = true, ResultCode = 7</td></tr>
<tr><td class="t">14:02:12.862</td><td><span class="lvl warn">warn</span></td><td class="src">script</td><td>ResultCode 7 is outside the accepted set {0, 1}</td></tr>
<tr><td class="t">14:02:12.870</td><td><span class="lvl error">error</span></td><td class="src">script</td><td>Rejecting recipe 42 (code 7) — see exception above</td></tr>
<tr class="dim"><td class="t">14:02:12.929</td><td><span class="lvl warn">warn</span></td><td class="src">runtime</td><td>Log truncated inline: 0 lines dropped (shown only when the per-run cap is hit)</td></tr>
</tbody>
</table>
</div>
</div>
<div class="where">
<div class="eyebrow" style="margin-bottom:6px">Where this ran</div>
<dl>
<dt>Site / node</dt><dd>site-a / node-a</dd>
<dt>Instance</dt><dd>DelmiaSim01</dd>
<dt>Target</dt><dd>DelmiaReceiver.ProcessRecipeDownload</dd>
<dt>Trigger</dt><dd>call</dd>
<dt>Log lines</dt><dd>5 here · 1 on other steps</dd>
</dl>
<div class="acts"><span class="btn sm">Open in Debug View</span><span class="btn sm">Audit rows (9)</span></div>
</div>
</div>
</div>
</section>
<!-- ───────────────────────────── node anatomy ─────────────────────────── -->
<section>
<h2>Node anatomy</h2>
<p class="lede" style="margin-bottom:12px">Nothing moves on a node that has no log lines. A node with lines gains a count badge in the footer, highest severity first; a failed node swaps its subtitle for the exception's type and message.</p>
<div class="grid3">
<div>
<div class="nodebox"><div class="body"><div class="head"><span class="eb">Inbound</span><span class="dur">1 842 ms</span></div><div class="title">DelmiaRecipeDownload</div><div class="foot"><span class="sub">flow-debugger · jdoherty</span><span class="lg"><i class="i">≡ 1</i></span></div></div></div>
<div class="cap"><b>Info only.</b> A faint <span class="mono">≡ n</span> badge. Debug lines count here too; the tab's <i>Warn +</i> filter hides them.</div>
</div>
<div>
<div class="nodebox"><div class="body"><div class="head"><span class="eb">Write</span><span class="dur">12 ms</span></div><div class="title">RecipeId, RecipeName</div><div class="foot"><span class="sub">RecipeId = 42, RecipeName = …</span><span class="lg"><i class="w">! 1</i></span></div></div></div>
<div class="cap"><b>Runtime warning on a step.</b> The write helper's own log line ("acked with quality Uncertain") lands on the write node, not on the script run, because the helper was inside its step scope when it logged.</div>
</div>
<div>
<div class="nodebox failed"><div class="body"><div class="head"><span class="eb">Script run</span><span class="dur">1 731 ms</span></div><div class="title">ProcessRecipeDownload</div><div class="foot"><span class="sub exc">InvalidOperationException: Recipe 42 rejected …</span><span class="lg"><i class="e">✕ 1</i><i class="w">! 1</i><i class="i">≡ 2</i></span></div></div></div>
<div class="cap"><b>Failed with an exception.</b> Red stroke and eyebrow as today; the subtitle becomes <span class="mono">Type: message</span>. Selecting it opens the Log tab first.</div>
</div>
</div>
</section>
<!-- ───────────────────────────── the author's side ────────────────────── -->
<section>
<h2>What the script author writes</h2>
<p class="lede" style="margin-bottom:12px">One new global, <code>Log</code>, on every script surface (site scripts, shared scripts, inbound API methods, the Test Run editor). Always forwards to the node's logger; becomes a trace row only when the run is traced, so untraced production runs add no audit volume.</p>
<pre class="code"><span class="c">// ProcessRecipeDownload — site Call script (excerpt)</span>
<span class="k">var</span> recipeId = Parameters.Get&lt;<span class="k">int</span>&gt;(<span class="s">"RecipeId"</span>);
Log.Info(<span class="s">$"Recipe {recipeId} requested for line {Parameters.Get&lt;string&gt;("Line")} by {Parameters.Get&lt;string&gt;("Requester")}"</span>);
Log.Debug(<span class="s">"Writing header attributes then DownloadRequest"</span>);
<span class="k">await</span> Children[<span class="s">"DelmiaReceiver"</span>].SetAsync(<span class="s">"RecipeId"</span>, recipeId);
<span class="c">// … writes, wait …</span>
<span class="k">var</span> code = <span class="k">await</span> Children[<span class="s">"DelmiaReceiver"</span>].GetAsync&lt;<span class="k">int</span>&gt;(<span class="s">"ResultCode"</span>);
Log.Info(<span class="s">$"Galaxy answered: RecipeProcessedFlag = true, ResultCode = {code}"</span>);
<span class="k">if</span> (code <span class="k">is not</span> (0 <span class="k">or</span> 1))
{
Log.Warn(<span class="s">$"ResultCode {code} is outside the accepted set {{0, 1}}"</span>);
Log.Error(<span class="s">$"Rejecting recipe {recipeId} (code {code})"</span>);
<span class="k">throw new</span> InvalidOperationException(<span class="s">$"Recipe {recipeId} rejected by Galaxy (code {code})"</span>);
}
Log.Warn(<span class="s">"Recoverable problem"</span>, caughtException); <span class="c">// an exception on a line renders as an expandable chip</span></pre>
</section>
<section>
<h2>Where a line lands</h2>
<div class="tbl-wrap">
<table class="plain">
<thead><tr><th>Produced by</th><th>Source</th><th>Lands on</th><th>How</th></tr></thead>
<tbody>
<tr><td><code>Log.*</code> in a site script (incl. inline shared scripts)</td><td>script</td><td>That execution's <b>Script run</b> node</td><td>One <code>ScriptTrace.ScriptLog</code> row per line, keyed by the run's <code>ExecutionId</code>; no step id</td></tr>
<tr><td><code>Log.*</code> in an inbound API method script</td><td>script</td><td>The <b>Inbound</b> node (or the debugger's route-call root for a script entry)</td><td>Same row, written centrally with the inbound <code>ExecutionId</code>; inline on a Live run through the runner's collector</td></tr>
<tr><td>The runtime's own warnings while a helper step runs (write, wait, sync API call)</td><td>runtime</td><td>That <b>step's</b> node</td><td>The helper pre-mints its step's event id and holds it in an ambient scope; the context's logger copies Information + lines into a row stamped <code>stepEventId</code></td></tr>
<tr><td>Runtime warnings outside any helper step</td><td>runtime</td><td>The <b>Script run</b> node</td><td>No scope active → falls back to the execution anchor</td></tr>
<tr><td>The exception that failed a script run</td><td>—</td><td>The <b>Script run</b> node's exception block</td><td>Not a log row: <code>ScriptRun.errorDetail</code> now carries <code>ex.ToString()</code> (type, message, stack, inner); the inbound row gets the same for a central method failure</td></tr>
<tr><td>Log lines from other actors on the node (instance actor, DCL)</td><td>—</td><td>Not captured in v1</td><td>No execution-id enrichment exists on the node loggers; recorded as the follow-up, not attempted here</td></tr>
</tbody>
</table>
</div>
</section>
<section>
<h2>Decision notes</h2>
<ol class="notes">
<li><b>Lines are audit rows, not a side channel.</b> A new <code>ScriptLog</code> kind in the existing <code>ScriptTrace</code> channel rides the collector for Live and the audit store for Replay, with redaction, retention and the site 7-day purge applying unchanged. The graph builder folds them into their owning step; they are never drawn as nodes.</li>
<li><b>Untraced runs pay nothing.</b> <code>Log.*</code> always writes to the node's ILogger at the given level (so it shows in Serilog and the site log as usual) and emits a row only when the run is traced. <code>TraceAllCalls</code> methods therefore gain lines in Replay at ~1 row per line.</li>
<li><b>Bounded by design.</b> A per-run cap (200 lines, configurable) and a 2 000-character message cap sit at the API; past the cap lines are dropped and one closing runtime warning names the count. Inline, log rows are low-priority in the collector: they stop 50 events short of the 500 cap so the step rows emitted at run end are never crowded out, and the page shows <i>log lines truncated inline — Replay is complete</i> when that happens.</li>
<li><b>Attribution is explicit, never inferred from time.</b> A row carries <code>stepEventId</code> or it belongs to the execution's anchor step. Steps whose row is written later by another actor (cached calls, notifications) keep the fallback.</li>
<li><b>Exceptions stay where they are already recorded.</b> The failure row exists on every path today; it only lacked the detail. Adding <code>errorDetail</code> costs one argument on the site and one internal field centrally; the HTTP caller still receives the safe <code>SCRIPT_ERROR</code> body.</li>
<li><b>Selecting a failed node opens the Log tab.</b> Every other node keeps Input as its first tab; the Output tab's red alert stays and links across.</li>
<li><b>Not in this slice:</b> live streaming of lines during a long run (Live rows still arrive with the reply), a run-wide chronological log view (the register's Direction B list would host it), and node-logger enrichment by execution id.</li>
</ol>
</section>
</div>