Every Conduction app runs its flows on OpenRegister's ONE flow engine
(ADR-065), so "what is running right now" has a single answer for the whole
instance — and yet no app could show it without rebuilding the same widget:
a controller or store, a list view, a poll timer, a status vocabulary, an
empty state. This absorbs all of that into one placement.
CnFlowRunsWidget (registry type `flow-runs`) reads OpenRegister's
`flow-runs/active` surface through the shared useEndpointSource engine and
renders one row per live run: status dot, flow NAME, current step, trigger,
coarse age. It shows ALL non-terminal statuses (queued / running /
suspended), because a run holds `running` only while a worker pass executes
it — a widget filtering on that would be empty almost always, and an empty
"running flows" widget looks identical whether the engine is idle or the
filter is wrong. It polls (default 15s, floored at 5s, `0` off), pauses
while the tab is hidden, and refetches once on return: a snapshot taken at
mount would be wrong within seconds and would look exactly like being right.
Four fixes came out of putting it on a real dashboard:
- `config.rowRoute` on a `type:"index"` page now works. It parsed, validated
and did NOTHING, so an index whose detail surface is not a `type:"detail"`
page — an authoring canvas, a form page — shipped rows that were dead on
click and indistinguishable from a broken table. It wins over the
register+schema detail lookup, and a route name the router does not have is
now reported by name instead of rejecting into a silent catch.
- CnStatWidget's VALUE no longer ellipsises. It shared a flex row with the
caption while being the only shrinkable item, so any caption longer than
the tile was wide won the space and the number — the entire point of a KPI
tile — rendered as "3.." while its decoration rendered in full. The caption
truncates now; the value never does.
- `layout[].borderless` decouples "no header" from "no card". They were one
decision, so a headerless TILE lost its card entirely and authors drew
their own bordered box inside the grid cell to get it back — a card inside
a card, which is the visual defect this flag removes.
- CnGraphCanvas lays out nodes that carry no coordinates. A node document is
not obliged to have x/y — a hand-written, imported or agent-generated graph
routinely has none — and `undefined + nodeWidth / 2` is NaN, so every edge
rendered as `d="M NaN NaN L NaN NaN"` (invisible, one console error per edge
per render) and every node got `left: undefinedpx`, an invalid declaration
the browser drops, collapsing the canvas into the static flow. A
coordinate-less graph did not degrade; it broke. Missing coordinates now
land on a deterministic grid, and because dragging emits the RESOLVED
position, the first move stores a real one.
Tests: 336 green across the touched areas — 19 new for the widget (statuses,
honest total from the endpoint rather than the rendered length, quiet empty
and error lines, poll interval + floor + hidden-tab pause + unmount
teardown, opt-in row clicks carrying the FLOW id, coarse age), 6 for
rowRoute precedence and the unknown-route warning, 4 for borderless, 5 for
the canvas auto-layout. Docs pages + generated partials for both new
components; check:docs and the JSDoc ratchet pass; `npm run lint` clean.
Not done here: a Vue-2 (`beta`) backport — the widget is authored on the
Vue-3 line, which is what the manifest-driven fleet consumes.