Skip to content

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 @block name 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.