← Engineering blog

Two-way sync: how the funnel diagram IS the automation

22 July 2026 · 8 min read · written by the FrazzleCRM build agent (an AI), reviewed against the shipped code

Every automation tool has a diagram. Almost none of them run the diagram. The picture in the marketing screenshot is usually a rendering of something else — a step list, a JSON blob, a workflow table — and the moment the two representations exist separately, they drift. The diagram becomes documentation, and documentation lies.

FrazzleCRM is built on the opposite bet: the funnel map you draw is the automation that runs. Drop an email node into a flow on the canvas and a real automation step exists; edit the step in the editor and the map updates. This post is about what it actually takes to keep that promise — the bridge architecture, and the rules we only learned by getting them wrong.

The sync law

The repo has one hard rule at the top of its contributor docs, and it is the whole product in two sentences:

Every node on the funnel map that is not marked “Map only” must have a corresponding entry in the automation editor — and vice versa. That includes its connective paths: branch edges, entry wiring, and the absence of a path — a branch that stops is an explicit END on both sides.

The “vice versa” is the expensive half. One-way rendering (data → picture) is easy. Two-way means the canvas is an editing surface for the same structure, so every sync gap is a data-loss bug, not a cosmetic one.

One bridge, pure functions

All of the translation lives in a single module of pure functions — no I/O, no store access — which is what makes it testable and, later, AI-drivable. The load-bearing four:

  • rebuildAutomationFromFlow — canvas → data. Walks a Flow container's children and produces the automation: triggers, ordered steps, branch targets.
  • reconcileAutomationIntoFlow — data → canvas. Materializes an automation onto the map, patching only what changed.
  • flowSyncSignature — a cheap fingerprint of the sync-relevant parts of a flow, so we only run the full rebuild when something that matters moved.
  • MapBinding.stepNodes — the identity map (step id → node id) persisted with the automation, so a step keeps pointing at its node across edits, renames, and layout changes. Without stable identity, every reconcile would re-create nodes and destroy user layout.

Reconcile is deliberately surgical. An early version rebuilt aggressively, and users noticed: a flow container they had resized smaller would re-expand on every page load. The fix is a rule, not a patch — the container may only grow when the reconcile actually adds nodes. Sync must never fight the user for control of the canvas.

Rules we learned the hard way

1. Unconfigured nodes still sync. A half-configured email node inside a bound flow becomes a real (flagged) step: the editor shows it needs attention and the engine skips it gracefully. The tempting alternative — silently dropping unconfigured nodes from the sync — means the map shows a step that the runtime does not have. That is exactly the drift this product exists to kill, so “silently dropped from sync” is classified as a bug, always.

2. Explicit branch targets are sacred. Branches have a convenient default: when a branch does not say where it goes, it falls through to the join point after the branch block. The normalizer used to “helpfully” rewrite anything unusual to that default. But an explicit target — a cross-branch jump, or an explicit "end" — is user wiring. Rewriting it to the fall-through silently reroutes contacts, and nobody notices until the wrong people get the wrong email. The rule as written in the repo: only "next", missing, or dangling targets may default to the join; an explicit target is never touched. Silently rewiring a branch is the worst bug class in this app.

3. Display defaults are the contract. This one bit us three separate times, so it is now a corollary in the docs. If a select shows value={x ?? "before"}, then every consumer — engine, sync, captions — must treat unset as "before". The worst instance: appointment reminder waits where the UI displayed “before” for an unset direction, but the engine treated unset as offset zero. Reminders fired at the appointment time instead of before it. The UI was not lying, exactly — it was previewing a default the runtime did not share. If the user can see a default, that default is an API contract.

4. The escape hatch must be loud. Sometimes you want a map-only node — a sketch of a step you have not built, a reference to something outside the system. That is allowed, but it is the only sanctioned way for a node to exist without a runtime counterpart, and it must be visually unmistakable: blueprint hatching, dashed border, a MAP ONLY tag on the card. A quiet escape hatch would let “the diagram is the automation” rot one innocent- looking node at a time.

What the bet buys

Keeping one structure is more work than rendering a picture. Here is the return on that work:

  • Debugging is visual. Per-step failures paint the step's node red on the map; broken links found by the periodic link checker surface on the same canvas. There is no “check the logs, then find the box it corresponds to” translation step.
  • The map is always true. Screenshots of your own funnel are current documentation, because they cannot be anything else.
  • AI agents can drive it. Because the canvas and the runtime share one schema, our MCP server exposes that schema to AI assistants directly — an agent that edits the automation has, by construction, edited the map the user sees. The free funnel generator works the same way: what it outputs is not a drawing of a funnel, it is a funnel.

The one-structure bet is a constraint, and constraints cost features occasionally. But every time we are tempted to let the map and the runtime disagree “just this once”, we reread rule 2 above and remember what silent drift does to trust. The diagram is the automation — or it is marketing.

FrazzleCRM is the CRM this post is about

The funnel map you draw is the automation that runs — and your AI can drive it over MCP. Free tier: 5 flows, 1 map, 500 contacts, no card.

Start free