ADR 0019: FBD layout is presentation, not configuration¶
Status: Accepted Date: 2026-07-11 Issue: #896
Context¶
An FBD diagram has two kinds of information: the network (blocks, their
parameters, and the wires between them — the logic that runs) and the
arrangement (where each block sits on the canvas — pixels that don't).
Legacy DCS platforms conflate them. In DeltaV, block X-Y coordinates are
saved into the underlying text configuration (the .fhx file), which
treats an arbitrary graphical artifact as code: an accidental nudge of a
block turns up in version history as a change, and upgrades or migrations
in regulated plants force engineers to reconcile diffs that have no
functional meaning whatsoever.
CNDCS was deliberately built the other way. The ControlModuleTemplate CR
stores only the network; the editor loads every block at the origin and
computes an arrangement with layoutFBNetwork(). Change control, audit,
and the ST projection (ADR 0012) all operate on logic alone.
The editor's in-session behavior, however, had drifted toward
position-keeping. #758 established that an ST edit must not re-layout the
parts of the diagram it didn't touch: after recompiling edited ST, blocks
whose identity survived kept their prior canvas coordinates, and only new
blocks took coordinates from the fresh layout. Those fresh coordinates
were computed for the whole graph, as if every block were at its layout
position — so when survivors snapped back to their old spots, a new block
could land on top of one of them. That is exactly what the #838
cm-template-authoring clip put on camera: an ST-added NOT block
materialized overlapping an existing block on flip-back. The editor
shipped a code path that produces an overlapping — visibly broken —
arrangement, in service of preserving positions the product never
promised to keep.
Decision¶
Block positions are presentation, not configuration. They are never
persisted, and the editor makes no effort to preserve a transient
arrangement across a network change: any recompile that changes the
network gets a full, clean auto-layout. layoutFBNetwork() is the one
sanctioned arrangement, and it must be deterministic and non-overlapping.
Alternatives Considered¶
- Preserve surviving positions, collision-nudge new blocks — keep the #758 behavior and push any new block whose layout coordinate overlaps a restored survivor to the nearest free spot. Rejected: it papers over the seam instead of removing it, keeps two arrangement authorities (the layout engine and the position-restore pass) whose interaction produced the bug, and protects an arrangement that evaporates on reload anyway.
- Persist coordinates in the CR — make the user's arrangement durable and skip layout entirely. Rejected outright: this is the DeltaV/fhx pathology the product was designed to avoid — graphical noise in change history and regulated-plant migration diffs.
- Do nothing — the overlap is rare and a manual drag fixes it. Rejected: the editor must never render two blocks stacked; the #838 shoot hit it in a five-block module on the product's flagship authoring flow.
Consequences¶
loadNetworkIntoEditor()(template editor live sync) drops the survivor position-restore pass; a changed network is fully re-laid-out. The #758 in-place-position contract is retired. The@blockname markers from #758 are unaffected — authored block names are logic and still survive recompiles.- The #752 verbatim hold stays: a zero-edit ST⇄FBD round trip skips the recompile entirely and keeps the held network — names, wires, and arrangement — untouched. That invariant is graph identity, not layout.
layoutFBNetwork()determinism and non-overlap are pinned by unit tests; a network that lays out badly (overlap, ambiguous wiring) is a layout-engine bug to fix there, not a reason to hand-place blocks.- Manual block dragging remains as a transient inspection aid (peeking under a wire bundle, presenting). It survives nothing: not a mode switch that recompiles, not a save, not a reload. UI affordances must not suggest otherwise.
- Reversal would mean persisting coordinates or reintroducing a position-preservation pass — both are single-file changes technically, but the first contradicts the product's change-control story and needs a schema addition, so in practice this decision is load-bearing for how diffs and audits read.