Pipeline field guide · 11 columns · 03 Jul 2026

Every column of the sales-pipeline board

Ways in, the station, ways out, and every button on the card — for all 11 board columns (app/dash_js.py:542-544), plus the hidden terminal state. Click any card, station, numbered row, or heading to leave an inline comment — it's the core feature of this guide, not decoration.

Column 1 of 11

🚫 Negative Replies

negative

A real stage (not derived). Any inbound reply the classifier reads as a "no" lands here for a human to decide: soften it back into the funnel, or close it out.

▸ Ways in

⚙️Mint classifier

Negative-reply classifier fires

Inbound reply parsed at mint time as a negative response → neg_reply=True.

from Cold inbox / mint · flows_inbound.py

🖱️Rep click

Rep manually marks negative

Status dropdown → Negative. deal_fsm.set_negative(actor="human").

from Any column · dash.py:1255-1265

🚫 Negative Replies3
HB
Horváth Bea
draft reply ready
Awaiting rep decision
+2
2 more waiting…

real stage · key negativeWhere a "no" waits for a human

Ways out ▸

⇄ moves
🖱️Rep click

Archive w/o reply

Confirm-gated, silent — no email. api_negative, dash.py:1837-1844.

→ goes to Lost (hidden)

⚠️ Signed EBO vs. live code — negative follow-up chain

EBO.md row 29 (plans/bnf-2026-07-02-02/EBO.md:104) says the old negative follow-up chain (auto goodbye → +2-day nudge → +7-day auto-ghost) was deleted entirely — Lost is terminal and silent. But this exact button is still fully live: "Send + Booking FUP" (dash_js.py:1043) calls api_negative(to_fup) (dash.py:1820-1831), which sets neg_fup_stage="awaiting_2day" + a +2-day next_followup_date, wired onward to api_negative(send_2day_fup) (dash.py:1832-1836) → flows.send_negative_followup — the exact chain the EBO says is gone. Which is correct: keep shipping this button, or delete it to match the signed EBO?

What every click on the card does

The card shown once a reply is read as negative.

HB
Horváth Bea
HB Dekor Kft.
1 Move to
NEGATIVE
2 Előzmény (history)
14:02 — inbound: "Köszönöm, most nem aktuális."
13:40 — sent: kapcsolatfelvétel #2
AI-drafted reply 3 (editable)

Semmi gond, ha később aktuálissá válik, szívesen segítünk!

4Send + Booking FUP
5Reply & Mark Lost
6Archive w/o reply
1

Move-to dropdown ⇄ moves

Reassign to any column manually.

2

Előzmény collapsed

Collapsed history banner — click to expand the touchpoint log.

3

AI-draft reply textarea

Editable draft. Nothing sends until a button below is clicked.

4

Send + Booking FUP ✉ sends ⇄ moves

dealNegFup — sends the draft, books a 2-day follow-up, moves to Contacted — No Appt. See the gap note above.

5

Reply & Mark Lost ✉ sends ⇄ moves

dealNegLost — sends a closing reply, moves to Lost.

6

Archive w/o reply ⇄ moves confirm-gated

dealNegArchive — silent close, no email, moves to Lost.

Source: app/dash_js.py:1033-1045 · app/dash.py:1255-1265,1794-1798,1820-1844

Column 2 of 11

New lead

new_lead

The entry column. A brand-new inbound, or a lead that came back to life, sits here until a rep arms an outreach sequence — arming is what pins it into the follow-up bay.

▸ Ways in

⚙️Default mint

Fresh inbound

A brand-new contact — GHL form, cold inbox — mints here by default.

from Cold inbox / GHL

🖱️Rep click

Manual move

Drag or status dropdown.

from Any column

New lead5
TL
Tóth László
no sequence armed
+4
4 more waiting…

real stage · key new_leadWhere a lead starts

Ways out ▸

✉ sends⇄ moves
🖱️Rep click

Rep arms a sequence

Sequence editor → Send & schedule. Email #1 goes now, pins the card. sequences.py:255-262.

→ goes to Contacted — No Appt.
⇄ moves
🖱️Rep click

Manual move

Drag or status dropdown.

→ goes to Any column

⚠️ Signed EBO §B1 vs. live code — third destination for replies

EBO.md §B1 (~lines 27-38) mandates every real-human reply lands in either negative or new_lead — explicitly forbidding a reply being silently classified is_automated with no card at all, and forbidding any third destination. But app/flows_inbound.py:365-405 shows: when the classifier marks a cold-inbox reply is_other=True with no CRM match, it creates only an Activity-Log "Review needed" row + a Slack ping — no board card in either column, a human must manually convert it. That's the third destination B1 forbids. Is this an acceptable interim gap, or does it need fixing before B1 can be considered shipped?

What every click on the card does

The card shown for a fresh, un-sequenced lead.

TL
Tóth László
toth-kertesz.hu
1 Move to
2 ÚJ · <2h 3 no website
Their message

"Érdekelne egy weboldal a kertészetnek, mennyibe kerülne?"

4 Send Emails → sequence editor
Step 1 · today · email  ·  Step 2 · +3d · email  ·  Step 3 · +7d · email
4aArm sequence (Send & schedule)
1

Move-to dropdown ⇄ moves

Reassign to any column manually.

2

"✨ ÚJ" chip <2h old

Green freshness chip, clears itself after 2 hours.

3

Website-missing badge conditional

Shows only when enrichment found no site.

4

Send Emails opens editor, not a reply panel

Opens the sequence editor (shown expanded above) — not the reply-approval panel. 4a Arm sequence ✉ sends ⇄ moves fires email #1 now and pins the card to Contacted — No Appt.

Source: app/sequences.py:255-262 · app/flows_inbound.py:365-405

Column 3 of 11 · Pipeline field guide · 03 Jul 2026

Contacted — No Appointment Booked

booking_fup Derived column

The holding bay between first contact and a booked call. It has no stage of its own — every card here is really a new_lead deal that some flag (a running sequence, a follow-up date, a cancelled meeting) pins into view. That's why so many roads lead in and out. Below: every way in, every way out, and what each button on the card does.

▸ Ways in

✉ sends
🖱️Rep click

Rep arms the follow-up sequence

Couldn't reach the lead → opens the sequence modal and hits Send & schedule. Email #1 goes now, the rest auto-fire.

from New Lead · the main road in

📞Phone

Rep logs a call + sets a follow-up date

Called, no answer — logs it and picks a follow-up date. No email is sent.

from New Lead

🖱️Rep click

No-show → rebook starts

Lead didn't show; rep starts the rebook sequence and the card returns to the follow-up bay.

from Sales Call

🖱️Rep click

Negative reply, softened

Rep answers a "no" with a polite close + a 2-day follow-up instead of writing it off.

from Negative Replies

🖱️Rep click

Manual move

Rep drags the card here, or picks the column in the status dropdown.

from Any column

Contacted — No Appt. 7
KZ
Kovács Zsolt
napfeny-kert.hu
Reply waiting · review
NB
Nagy Bea
sequence running · grey
+5
5 more in the bay…

a display state, not a stage — key booking_fup Where a lead waits

Ways out ▸

⇄ moves
⏱️Time elapsed

Silence after the last nudge

No reply 2 days after the final nudge (cadence day 2 / 5 / 9) → auto-ghosted.

→ goes to Ghosted
⇄ moves
🖱️Rep click

Rep marks it dead

Status dropdown → Lost. No goodbye email — Lost is silent.

→ goes to Lost (hidden)
⇄ moves
🖱️Rep click

Manual move

Rep drags it anywhere, or picks another column in the dropdown.

→ goes to Any column

Looks like an exit — but the card stays put

  • A negative reply on a card that has an armed sequence pulses for review but stays here (WJ-56) — the sequence already proved real engagement, so the rep decides.
  • The sequence stepping through day 2 / 5 / 9 is progress, not a move.
  • The card turning grey while a sequence runs is a state cue ("nothing to do"), not a column change.

⚠️ No-show rebook pins before the sequence is armed

app/flows_calls.py:166-175 noshow_define() attaches the rebook sequence un-armed (a WS7 preview modal is meant to arm it afterward). But deal_fsm.py:103-104 (if seq_type=="noshow_rebook" and not seq_done: return "booking_fup") has no check on seq_armed_at, so the card is already pinned here at DEFINE time, before the rep ever arms it. dealNoShowRebook (dash_js.py:1910-1919) also optimistically moves the card on click, before the outcome API even returns. Is pinning-before-arming intentional (so the rep doesn't lose track of it), or should it wait for the arm click?

What every click on the card does

The card a lead shows in this column. Numbered controls map to the list on the right — including whether a click can reach the lead () or move the card ().

KZ
Kovács Zsolt
Napfény Kert Kft.
1 Move to
2🌐 Site 3✉ Copy 3📞 Copy 4ℹ Details 5🕑 History
Their last message

"Kösz, hogy jelentkezett — most épp sok a meló, de érdekelne. Írjon jövő héten?"

Sequence running · day 2 / 5 / 9 6 Edit & reschedule
Reply waiting · appears only when they write back

AI drafted a response. Nothing goes out until you approve it.

7 Approve & send Mark read
8📞 Log call
8a📅 Book appointment 8b🗓 Set follow-up
8cMark as lost
1

Move-to dropdown ⇄ moves

Reassign the card to any column — Booked, Lost, back to New Lead. Same effect as dragging it.

2

Site

Opens the lead's website in a new tab. Read-only.

3

Copy email / phone

Copies the address or number to the clipboard. Never contacts the lead.

4

Details

Opens the info drawer — editable fields, enrichment (location, top-3 services), and the Missive / Notion links.

5

Full history

Every touchpoint with a one-line AI summary; add a private note or copy a transcript.

6

Edit & reschedule while running

Reopens the sequence modal to tweak steps or timing. Doesn't send — it only re-schedules; the send stays behind the arm click.

7

Approve & send ✉ sends human gate only if they replied

Appears with the reply pulse. Sends the AI-drafted answer to the lead — the one click that reaches them. Mark read clears the pulse without sending.

8

Log call → three outcomes

8a Book appointment ✉ sends ⇄ moves — creates the Google Meet, sends the invite, advances to Booked.  8b Set follow-up — stamps a follow-up date, no email, stays here.  8c Mark as lost ⇄ moves — sends it to Lost.

Source: verified against deal_fsm.py, flows_inbound.py, flows_calls.py, sequences.py, dash_js.py + the signed EBO. Precedence in deal_fsm.derive_column, deal_fsm.py:92-121: undisposed neg_replycancelled_at → unfinished noshow_step → unfinished seq_type==noshow_rebook (no armed check) → first_contact_stage → awaiting_reply/nudge/armed-seq/next_followup_date — all only when base column is new_lead.

Column 4 of 11

Booked / Sales Call Prep

booked

A call is on the calendar. The rep preps the niche + script before the call-outcome buttons unlock.

▸ Ways in

Booked / Sales Call Prep4
SZ
Szabó Anna
Thu 15:00 · not prepped

real stage · key bookedWhere a call waits for prep

Ways out ▸

⇄ moves
🖱️Rep click

Mark Prepped

dealPrep — dash_js.py:1898-1900.

→ goes to Sales Call / Proposal
🖱️Rep click

Reschedule

Stays here — "🔄 Átütemezve" chip appears.

stays in Booked

⚠️ Three-way disagreement on "Open in Studio" placement

specs/HAPPY_PATH.md:46-48 (Phase 2, card in Booked / Sales Call Prep) describes the Studio prep chain running full-auto until Figma assets are ready, with a Figma icon appearing on the card while still in Booked. EBO.md row 48 (~line 168) says "Open in Studio" is gated stage ≥ sales_callnot shown on Booked. Code splits the difference: dash_js.py:1088 renders studioOpenLink ("Open in Studio") inside the booked branch itself, while the Figma-specific button (studioFigmaBtn) only appears in the sales_call branch (dash_js.py:1138). HAPPY_PATH says Booked, EBO says Sales-Call-only, code does partial-Booked/partial-Sales-Call — which is the intended UX?

What every click on the card does

The prep card, before "Mark Prepped".

SZ
Szabó Anna
szabo-uveg.hu
1 Move to
SALES CALL PREP 2 Thu 15:00
Hint

Prep the niche + script before the call. The call-outcome buttons (Move Forward · No-show) appear after "Mark Prepped".

3Open in Studio 4Open in CRM
5Mark Prepped
1

Move-to dropdown ⇄ moves

Reassign to any column manually.

2

Call-date chip "🔄 Átütemezve" if rescheduled

Amber if rescheduled; "⚠️ Meghívó nem ment ki" red if the invite email failed.

3

Open in Studio see gap note above

studioOpenLink, dash_js.py:1088 — deep-links to the design brief in Studio.

4

Open in CRM

Opens the Notion deal page. Read-only shortcut.

5

Mark Prepped ⇄ moves

dealPrep — unlocks the call-outcome buttons and moves to Sales Call / Proposal.

Source: app/dash_js.py:1076-1095,1853-1875,1898-1900

Column 5 of 11

Sales Call / Proposal

sales_call

The call happened (or didn't). The transcript pipeline runs in the background; the rep decides whether to move the deal forward, reschedule, rebook, or close it out.

▸ Ways in

⇄ moves
🖱️Rep click

Mark Prepped

dealPrep from Booked.

from Booked

Sales Call / Proposal3
PG
Papp Gábor
call done · 38 perc
Átirat: Kész

real stage · key sales_callWhere the pitch happens

Ways out ▸

⇄ moves
🖱️Rep click

Move Forward

dealForward opens the post-call wizard, sends the proposal/contract.

→ goes to Sign FUP
⇄ moves
🖱️Rep click

Mark Lost

dealLost.

→ goes to Lost (hidden)
⇄ moves
🖱️Rep click

No-show (reschedule or rebook)

dealNoShowReschedule / dealNoShowRebook.

→ back to Contacted — No Appt.

What every click on the card does

The card after the call happened.

PG
Papp Gábor
papp-epito.hu
1 Move to
PREPPED Sales call megtörtént · 38 perc
3 Átirat / összefoglaló
Átirat: Kész
"A ügyfél 3 aloldalt kér, június végi határidővel…"
4 Sales call feldolgozva — küldhető az ajánlat
5Move Forward
6 No-show options
6aNo show + reschedule
6bNo show + rebook seq.
7Mark Lost
8Design küldése ügyfélnek 9Saját aláíró link
1

Move-to dropdown ⇄ moves

Manual reassign.

2

Call-done tag callDone

Green "Sales call megtörtént · N perc".

3

Transcript pill loading / done / failed

Amber "Átirat feldolgozása…", green "Kész", red "Átirat sikertelen — újra".

4

Processed banner callProcessed

Confirms the AI summary is ready to fuel the proposal.

5

Move Forward ✉ sends ⇄ moves

dealForward — opens the post-call wizard, sends the proposal/contract, advances to Sign FUP.

6

No-show options ⇄ moves

6a reschedule stays here; 6b rebook sequence moves back to Contacted — No Appt (see the pin-before-arm gap note there).

7

Mark Lost ⇄ moves

dealLost.

8–9

Design küldése / Saját aláíró link ✉ sends gated on not-yet-sent

Studio send button + the rep's own DocuSeal signer link. Figma button (studioFigmaBtn) also lives here.

Source: app/dash_js.py:1096-1139

Column 6 of 11

Sign FUP

contract_sent · proposal_sent · proposal_accepted

The proposal/contract went out. This column tracks nudge cadence until both parties sign.

▸ Ways in

⚙️System

Post-call wizard sends the offer

Stage key becomes contract_sent/proposal_sent/proposal_accepted. deal_fsm.py:38.

from Sales Call · via Move Forward

Sign FUP2
MJ
Molnár Judit
day 4 in stage

real stage groupWhere signatures are chased

Ways out ▸

⇄ moves
⚙️System

Both parties sign

contract_signed stage_key. deal_fsm.py:39.

→ goes to Proposal Signed

What every click on the card does

Waiting on signature.

MJ
Molnár Judit
molnar-butor.hu
1 Move to
PROPOSAL SENT 3 Day 4 4 Valid until Jul 12 5 Nudge 2/3
1

Move-to dropdown ⇄ moves

Manual reassign.

2

Stage tag PROPOSAL SENT / AWAITING PAYMENT

Reflects which underlying stage_key the deal is on.

3

Days-in-stage chip

How long it's been waiting.

4

Offer-valid-until chip

The proposal's expiry date.

5

Reminder-index tag nudge cadence

Which auto-nudge step the deal is on.

Source: app/dash_js.py:1140-1146 · app/deal_fsm.py:38-39

Column 7 of 11

Proposal Signed / Fizetés FUP

contract_signed

Both parties signed. Waiting on the deposit or full payment to arrive.

▸ Ways in

⚙️System

contract_signed stage_key

Both signatures land on DocuSeal.

from Sign FUP

Proposal Signed2
RB
Ruzsa Béla
⚠ overdue 3 days

real stage · key contract_signedWhere payment is chased

Ways out ▸

⇄ moves
⚙️System

Deposit / full payment matched

±2% or 2000 HUF tolerance. deal_fsm.py:42.

→ goes to Payment Arrived

What every click on the card does

Signed, unpaid.

RB
Ruzsa Béla
ruzsa-agro.hu
1 Move to
SIGNED — AWAITING PAYMENT ⚠️ OVERDUE
1

Move-to dropdown ⇄ moves

Manual reassign.

2

Stage tag

"SIGNED — AWAITING PAYMENT".

3

Overdue tag payment_overdue

Red, appears once past the due date.

Source: app/dash_js.py:1147-1150 · app/deal_fsm.py:39,42

Column 8 of 11

Payment Arrived / Adatbekérő FUP

deposit_paid · full_paid

Money's in. Waiting to kick off the build.

▸ Ways in

⚙️System

deposit_paid / full_paid stage_key

Stripe/bank match confirms payment.

from Proposal Signed

Payment Arrived1
FI
Farkas Ildikó
deposit paid

real stage groupWhere the build kicks off

Ways out ▸

⇄ moves
⚙️System

Build stage_keys progress

waiting_dev etc. deal_fsm.py:43.

→ goes to 🏗 Ongoing build

What every click on the card does

Paid, pre-build.

FI
Farkas Ildikó
farkas-optika.hu
1 Move to
PAYMENT ARRIVED
3Create Task
1

Move-to dropdown ⇄ moves

Manual reassign.

2

Payment tag green

Confirms the match.

3

Create Task

Adds a Main-Tasks entry for the build kickoff.

Source: app/dash_js.py:1151-1154 · app/deal_fsm.py:42-43

Column 9 of 11

🏗 Ongoing build

waiting_dev · website_built · final_paid · handed_over

The site is being built. Ends when the retainer starts.

▸ Ways in

⚙️System

Build stage_keys

waiting_dev / website_built / final_paid / handed_over.

from Payment Arrived

🏗 Ongoing build2
VK
Vincze Katalin
paid · website_built

real stage groupWhere the site gets built

Ways out ▸

⇄ moves
🖱️Rep click

Start marketing retainer

dealMarketing. dash_js.py:1938-1940.

→ goes to 📈 Ongoing marketing

What every click on the card does

Build in progress.

VK
Vincze Katalin
vincze-cukraszda.hu
1 Move to
🏗 ONGOING BUILD PAID
3Confirm Invoice Sent
4Start marketing retainer
5Create Task
1

Move-to dropdown ⇄ moves

Manual reassign.

2

PAID tag conditional

Shows once the build payment is confirmed.

3

Confirm Invoice Sent

Marks the invoice step done — bookkeeping only.

4

Start marketing retainer ⇄ moves

dealMarketing — advances to Ongoing marketing.

5

Create Task

Adds a Main-Tasks entry.

Source: app/dash_js.py:1155-1162,1938-1940

Column 10 of 11

📈 Ongoing marketing

marketing

Retainer client. Terminal on the board — no further column to move to.

▸ Ways in

🖱️Rep click

Start marketing retainer

marketing stage_key / dealMarketing click.

from 🏗 Ongoing build

📈 Ongoing marketing6
BP
Balogh Péter
retainer · monthly

real stage · key marketingWhere retainer clients live

Ways out ▸

⚙️None

Terminal

No further column — the retainer is the end state on the board.

— none —

What every click on the card does

Retainer running.

BP
Balogh Péter
balogh-marketing.hu
1 Move to
📈 ONGOING MARKETING
2Create Task
1

Move-to dropdown ⇄ moves

Manual reassign — the only way out.

2

Create Task

Adds a Main-Tasks entry (monthly retainer deliverable).

Source: app/dash_js.py:1163-1166

Column 11 of 11

👻 Ghosted

ghost

Silence won. Auto-reactivates the instant the lead writes back.

▸ Ways in

⏱️Time elapsed

Negative-FUP ghost sweep

2 days silence after the final nudge. flows_calls.py.

from Contacted — No Appt.

⏱️Time elapsed

Unreachable timeout

No contact channel responds within the window.

from Contacted — No Appt.

👻 Ghosted9
NN
Nagy Norbert
silent 14 days

real stage · key ghostWhere silence goes

Ways out ▸

What every click on the card does

Silent, waiting for a reply.

NN
Nagy Norbert
nagy-fuves.hu
1 Move to
👻 GHOSTED
Summary

14 napja nem válaszolt. Ha ír, automatikusan visszakerül a New lead oszlopba.

4Create Task
1

Move-to dropdown ⇄ moves

Manual reassign.

2

👻 GHOSTED tag

Static state marker.

3

Hungarian summary text

Explains the auto-reactivation rule to the rep.

4

Create Task

Adds a manual follow-up reminder outside the sequence system.

Source: app/dash_js.py:1167-1170

Terminal state — off (Lost)

A 12th state that is not in BCOLS and never appears as a board column. Reached from Negative ("Reply & Mark Lost" / "Archive w/o reply"), Contacted — No Appt. ("Mark it dead"), or Sales Call ("Mark Lost"). It is terminal (no further transitions) and silent — no goodbye email is sent. A card here is invisible on the board except through the "⚠️ Needs attention" orphan bucket or a separate lost() view.

Trigger lines — what fires the move

🖱️Rep click
✉️Email — inbound or a lead link-click
⏱️Time elapsed
📞Phone call
⚙️System / auto
✉ sends reaches the lead — always behind a human click ⇄ moves the card changes column target behaviour (signed EBO) — see footnote

State-cue classes — app/dash_styles.py

.pulse-unread reply waiting — red pulsing border
.autoflow armed sequence running — indigo dashed
.booked-reminder blue dashed reminder
.needs-arm unarmed sequence pending — red border
.passive 55% opacity, clears on hover
.overdue payment overdue — solid red
.save-failed background save failure — red left border
.call-processed pulsing green border
.fresh2h / .fresh24h recency bar — new_lead only

⚠️ Signed EBO vs. code — pulse color + stage scope

EBO.md (multiple rows, e.g. ~lines 23, 92, 100, 116, 126) mandates a yellow pulsing animation for any reply on an existing carded lead at any stage. Code (app/dash_styles.py:140, .ucard.pulse-unread{border-color:#fecaca...}) is red/pink, not yellow, and dash_js.py:1184 additionally gates it to stage!=="booked" only — not universal across every stage as the EBO demands. Recolor to yellow + extend to all stages, or update the EBO to match red + booked-excluded?

Live vs. target

Arrows show the model in the owner-signed EBO (plans/bnf-2026-07-02-02/EBO.md). Most are live today. Five known gaps are called out inline above (negative send-fup chain, B1 carding, no-show pin-before-arm, Booked Studio-button placement, pulse color/scope) — each ends in a question for Mátyás.

Why "derived" (booking_fup only)

No card carries booking_fup as its stage. deal_fsm.derive_column shows a new_lead deal there whenever a pinning flag is set — a running sequence, a follow-up date, a cancelled booking, or a no-show loop. Every other column above is a real stage_key.

Source

Verified against app/dash_js.py:542-1170, app/deal_fsm.py, app/flows_inbound.py, app/flows_calls.py, app/sequences.py, app/dash.py, app/dash_styles.py + the signed EBO. Trigger types: 🖱️ click · ⏱️ time · ✉️ email · 📞 phone · ⚙️ system.