Flows canvas lane · 2026-08-06

Six things nobody has planned yet, drawn so you can rule them

Everything the lane had a plan for is built and live. What is left is six items that were never designed — named in the state file, boarded, and until now only prose. Each one below is drawn, its states shown, and ends in a fork with a recommendation.

How to use this. Nothing here is built. Read an item, look at the drawing, and answer its fork — a number is enough. Where a drawing shows the flow canvas it is in that screen's own measured ManyChat palette, because that screen is the one ruled exemption from the token sheet.
The six:
  1. The AI node — the brief is given; this draws it, and asks the one thing the brief does not settle
  2. Making a match mode — four of them can be seen and removed, never created
  3. Three wizard forks — built on the recommendation, live, each a small edit to flip
  4. Ref URL, QR and Ads triggers — deferred with "don't abandon it"
  5. One human, two contacts — the consequence the channel ruling parked
  6. Flow delivery analytics — the stats strip with no source
A

The AI node

one question in the way

The brief is captured — manychat-capability-catalog.md:443-455, screenshot 104. Two textboxes and one button, deliberately minimal, and explicitly not the Actions-node Claude form with its model picker and system prompt. Drawn below exactly as captured. The brief settles the form and does not settle what the button does — that is the fork, and it is the whole item.

The panel, as captured
AI stepClose panel
Tell AI what to do ?
Set a goal for the conversation
Give AI context ?
Share all the info
✦ Generate

Verbatim from the capture: the placeholders, the two tooltip discs, one blue button with a sparkle. No model picker, no temperature, no system prompt.

On the canvas
Whichever channel started it
AI step
Book a viewing for a 2-bed in Dubai Marina
Then

The node prints its GOAL, the way a message node prints its words — the card is the thing, not a label saying "AI step" over an empty box.

Every state
Empty
Set a goal for the conversation
✦ Generate

Generate is dead until there is a goal.

Ready
Book a viewing for a 2-bed in Dubai Marina
✦ Generate

A goal is enough; context is optional.

Working
Book a viewing for a 2-bed in Dubai Marina
Generating…

The one state that costs money. It must say so before it starts.

Refused
Book a viewing…

The model could not be reached. Nothing was changed.

✦ Try again

Errors are data: the draft is untouched.

What the brief does not say, and the app already has an answer for: this repo already ships an ai_handover node kind, titled "AI agent", whose executor RELEASES the thread to our own agent and ends the flow's turn. ManyChat's AI Step, with a "Generate" button, reads like something else entirely: a thing that AUTHORS steps onto the canvas. Those are not the same node and should not wear the same name.

The fork — what does Generate do?

  1. It hands the conversation to our agent, and the two boxes are its brief. "Generate" becomes "Hand over", the goal and context become the agent's mandate for this thread, and the node is the ai_handover we already execute. Nothing new in the engine; the node finally gets the panel it never had. Recommended — it is the only reading where the node works the day it ships.
  2. It writes steps onto the canvas for you. A build-time author: press it and messages, questions and conditions appear, which you then edit. Much larger, spends money at authoring time, and needs an undo story for a button that rewrites your graph.
  3. Both, as two different nodes. Honest to ManyChat, and doubles the surface for a node neither of us has watched anyone use.
B

Four match modes can be seen and removed, never created

half a feature

The engine matches six ways. The wizard makes two. The other four render as a dashed row with a Remove, so a trigger that already has one can be read and unmade — but nothing in the app can make one. Frames 32-34 offer no control for it. And it has never met real data: the one prod flow carries no trigger at all.

Today — what a fifth mode looks like
When a comment…
contains · invest contains · buy
begins with book Remove

Readable. Removable. Not creatable — there is no control that makes this row.

Proposed — the mode is a dropdown on the row
When a comment…
contains

is · contains · contains whole word · begins with · doesn't contain · is about

invest × buy × + word
+ Add another condition

One dropdown, the engine's own six, in ManyChat's own words. The chips are already built; only the mode picker is new.

The one that is not like the others: is_about is AI intent, not text matching — it costs a model call per inbound message. If it appears in the same dropdown as "contains" it will be chosen as casually, on every trigger, by someone who cannot see the bill.

The fork

  1. All six in one dropdown, with is_about carrying a cost note. One control, the engine's full vocabulary, and the expensive one says it is expensive. Recommended.
  2. Five in the dropdown; is_about is its own choice earlier in the wizard. ManyChat does this — "Detect words" and "Recognize intention" are two cards before the config. Truer to the walk, one more step for the common case.
  3. Leave it. Two modes cover every live keyword we have. The dashed row stays as a reader for anything authored elsewhere.
C

Three wizard forks, built on the recommendation and live

flip or confirm

Each of these shipped on the recommendation because the lane could not stop for an answer. They are live now. Each is a small edit to flip, and none is a rewrite.

1 — headings

Live: a question

Say what a comment must contain
contains · invest

Alternative: a statement

A comment must contain
contains · invest

Flipping is the q: strings in trigger-wizard.ts plus one test.

2 — include / exclude

Live: one list, a mode per row

contains · invest doesn't contain · job

Alternative: two boxes

Must contain invest Must not contain job

Two boxes read faster; one list is what the engine actually stores.

3 — the unwalked sub-forms
kindoursevidence
Live commentskeywords onlymatches capture 139
Contact events13 events + conditionsmatches capture
Story repliesscope, filter, delay3 of 4

The missing fourth is "React with ❤️ in response". It is in the schema, absent from the wizard, and unactionable — there is no reaction call on the Instagram client. Building the control without the capability recreates the trap we just removed elsewhere.

The fork

  1. Confirm all three as they are. Nothing changes; the block leaves the state file. Recommended — every one shipped on the reading the walk supports.
  2. Flip the headings to statements. One string change per step, plus a test.
  3. Split include / exclude into two boxes. A panel change only; the stored shape is untouched.
  4. Build the ❤️ reaction anyway, which means first finding out whether Meta exposes a reaction on a story reply at all. Unknown today.
D

Ref URL, QR code and Ads triggers

greyed with a reason

Deferred 2026-07-24 with "don't abandon it". They sit in the trigger picker today, greyed, with the honest reason on them. What has never been drawn is what they look like when they are not greyed.

Today — in the picker, greyed
Ref URL, QR code or Ad
Needs link tracking and Meta Ads Manager wiring. Boarded, not built.
Proposed — the Ref URL config
This flow starts when someone opens
ig.me/m/silveroakglobal?ref=marina-2bed

The ref is yours to name. It comes back on the first message and is what tells the flows apart.

Copy linkDownload QR

The QR is the same link as an image — one trigger, two ways to hand it out, which is why they are one picker row and not three.

What each actually needs. Ref URL: nothing new from Meta — the ref rides the first inbound message, so this is a trigger kind plus a link builder. QR: the same, plus an image. Ads is the odd one — it needs Ads Manager wiring and one Ads trigger per automation is a Meta cap we hit during the walk.

The fork

  1. Build Ref URL + QR now, leave Ads greyed. They share one implementation and need nothing from Meta we do not have. Ads keeps its honest reason. Recommended.
  2. All three together, and accept that Ads brings Ads Manager with it.
  3. Keep all three greyed until a campaign actually needs a trackable link.
E

One human is two contacts, and the flow cannot tell

data, not paint

Ruled 2026-08-03: a flow adapts per run and can branch on channel. The branch shipped this week. The consequence did not. Contact is keyed (channel, channelUserId), so the same person reaching us on Instagram and on WhatsApp is two rows — and a tag, a field or a CRM write on one is invisible to the other.

What happens today
stepInstagram runWhatsApp run
Asks the budget, saves itbudget = 2Mbudget unknown
Condition: budget over 1M?Yes branchNo branch — asks again
Tags hot-leadtaggednot tagged
Writes the CRM leadone leada second lead

The customer answers the same question twice and appears in the CRM twice. Nothing is broken in code — the key simply says these are two people.

Proposed — a person, above the contacts
Aisha K.
Instagram · @aisha.k  ·  WhatsApp · +971 50 …

Fields and tags hang off the PERSON. Each channel keeps its own thread, window and consent, because Meta's rules are per channel.

What joins them
signalconfidence
Same phone number capturedcertain
Same email capturedcertain
Operator joins them by handcertain
Same name + same daya guess — never

Only a captured identifier or a human joins two rows. Nothing is merged on a resemblance.

The fork

  1. A Person row above Contact, joined only on a captured phone or email, or by hand. Fields, tags and CRM identity move up; threads, windows and consent stay per channel. Additive, and the only option that stops the double CRM lead. Recommended.
  2. Copy fields across on a match, leave the rows separate. Cheaper, and drifts the moment one side changes.
  3. Leave it and say so on screen. The canvas warns when a flow has triggers on two channels. Honest, costs nothing, fixes nothing.
F

Flow delivery analytics — the strip with no source

promised, unrecorded

Sent, Delivered, Opened, Clicked was promised for the flow screen. Nothing records it: FlowRunEvent stores no wamid, so a flow send cannot be joined to the delivery webhook that already arrives and is already stored. The published view today shows Runs, Going on now and a per-step table — real numbers, and not these.

The honest limit today
stepreachederrors
Message · "Thanks for…"221
Question · budget150

Reached counts the people who got to a step and errors counts the times it failed. Nothing records deliveries, opens or clicks inside a flow, so those are not shown.

That sentence is on the screen now. It is true, and it is the reason this item exists.

Proposed — what one column costs
stepreachedsentdelivered
Message · "Thanks for…"222221
Question · budget151515
Sent and Delivered are one field away. The status webhook already arrives, is already keyed by wamid, and is already stored. Add wamid to the run event when a send succeeds and the join exists. Opened and Clicked are not: Meta gives no open signal on these surfaces, and a click needs the campaign link-wrapper a flow does not use.

The fork

  1. Sent + Delivered only, and drop Opened/Clicked from the promise. One nullable column, one join, two honest numbers — and the screen stops implying two it can never fill. Recommended.
  2. Add Clicked too, which means routing flow links through the campaign wrapper and changing what the customer sees in the URL.
  3. Leave the strip unbuilt and keep the sentence that says why.