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.
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
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 manually marks negative
Status dropdown → Negative. deal_fsm.set_negative(actor="human").
from Any column · dash.py:1255-1265
real stage · key negativeWhere a "no" waits for a human
Ways out ▸
Send + Booking FUP
Sends the AI-drafted reply and books a 2-day follow-up. api_negative(to_fup), dash.py:1820-1831.
Reply & Mark Lost
Sends a closing reply, archives to Lost. dash.py:1794-1798.
Archive w/o reply
Confirm-gated, silent — no email. api_negative, dash.py:1837-1844.
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?
The card shown once a reply is read as negative.
Semmi gond, ha később aktuálissá válik, szívesen segítünk!
Reassign to any column manually.
Collapsed history banner — click to expand the touchpoint log.
Editable draft. Nothing sends until a button below is clicked.
dealNegFup — sends the draft, books a 2-day follow-up, moves to Contacted — No Appt. See the gap note above.
dealNegLost — sends a closing reply, moves to Lost.
dealNegArchive — silent close, no email, moves to Lost.
Source: app/dash_js.py:1033-1045 ·
app/dash.py:1255-1265,1794-1798,1820-1844
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
Fresh inbound
A brand-new contact — GHL form, cold inbox — mints here by default.
from Cold inbox / GHL
Ghosted lead replies
Any reply on a Ghosted card reactivates it here (G8). flows_inbound.py.
from Ghosted
Manual move
Drag or status dropdown.
from Any column
real stage · key new_leadWhere a lead starts
Ways out ▸
Rep arms a sequence
Sequence editor → Send & schedule. Email #1 goes now, pins the card. sequences.py:255-262.
Manual move
Drag or status dropdown.
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?
The card shown for a fresh, un-sequenced lead.
"Érdekelne egy weboldal a kertészetnek, mennyibe kerülne?"
Reassign to any column manually.
Green freshness chip, clears itself after 2 hours.
Shows only when enrichment found no site.
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
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
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
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
An "ongoing project" first reply
A brand-new inbound whose thread shows an existing project skips New Lead and lands straight here.
from Cold inbox
Lead cancels a booked meeting
Lead clicks Cancel in the meeting email before it happens — the booking is torn down and the card drops back here.
from Booked
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
Negative reply, softened
Rep answers a "no" with a polite close + a 2-day follow-up instead of writing it off.
from Negative Replies
Manual move
Rep drags the card here, or picks the column in the status dropdown.
from Any column
a display state, not a stage — key booking_fup
Where a lead waits
Ways out ▸
Lead picks a slot
Opens the booking link and chooses a time — the goal of the whole bay.
Silence after the last nudge
No reply 2 days after the final nudge (cadence day 2 / 5 / 9) → auto-ghosted.
Lead replies, still keen
A positive reply mid-sequence stops the sequence and resurfaces the card for a human look.
Rep marks it dead
Status dropdown → Lost. No goodbye email — Lost is silent.
Manual move
Rep drags it anywhere, or picks another column in the dropdown.
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?
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 (⇄).
"Kösz, hogy jelentkezett — most épp sok a meló, de érdekelne. Írjon jövő héten?"
AI drafted a response. Nothing goes out until you approve it.
Reassign the card to any column — Booked, Lost, back to New Lead. Same effect as dragging it.
Opens the lead's website in a new tab. Read-only.
Copies the address or number to the clipboard. Never contacts the lead.
Opens the info drawer — editable fields, enrichment (location, top-3 services), and the Missive / Notion links.
Every touchpoint with a one-line AI summary; add a private note or copy a transcript.
Reopens the sequence modal to tweak steps or timing. Doesn't send — it only re-schedules; the send stays behind the arm click.
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.
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_reply → cancelled_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.
A call is on the calendar. The rep preps the niche + script before the call-outcome buttons unlock.
▸ Ways in
Rep books a call from the follow-up bay
dealBook creates the Google Meet + sends the invite. dash_js.py:1853-1875.
from Contacted — No Appt.
real stage · key bookedWhere a call waits for prep
Ways out ▸
Mark Prepped
dealPrep — dash_js.py:1898-1900.
Reschedule
Stays here — "🔄 Átütemezve" chip appears.
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_call —
not 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?
The prep card, before "Mark Prepped".
Prep the niche + script before the call. The call-outcome buttons (Move Forward · No-show) appear after "Mark Prepped".
Reassign to any column manually.
Amber if rescheduled; "⚠️ Meghívó nem ment ki" red if the invite email failed.
studioOpenLink, dash_js.py:1088 — deep-links to the design brief in Studio.
Opens the Notion deal page. Read-only shortcut.
dealPrep — unlocks the call-outcome buttons and moves to Sales Call / Proposal.
Source: app/dash_js.py:1076-1095,1853-1875,1898-1900
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
Mark Prepped
dealPrep from Booked.
from Booked
real stage · key sales_callWhere the pitch happens
Ways out ▸
Move Forward
dealForward opens the post-call wizard, sends the proposal/contract.
Mark Lost
dealLost.
No-show (reschedule or rebook)
dealNoShowReschedule / dealNoShowRebook.
The card after the call happened.
Manual reassign.
Green "Sales call megtörtént · N perc".
Amber "Átirat feldolgozása…", green "Kész", red "Átirat sikertelen — újra".
Confirms the AI summary is ready to fuel the proposal.
dealForward — opens the post-call wizard, sends the proposal/contract, advances to Sign FUP.
6a reschedule stays here; 6b rebook sequence moves back to Contacted — No Appt (see the pin-before-arm gap note there).
dealLost.
Studio send button + the rep's own DocuSeal signer link. Figma button (studioFigmaBtn) also lives here.
Source: app/dash_js.py:1096-1139
The proposal/contract went out. This column tracks nudge cadence until both parties sign.
▸ Ways in
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
real stage groupWhere signatures are chased
Ways out ▸
Both parties sign
contract_signed stage_key. deal_fsm.py:39.
Waiting on signature.
Manual reassign.
Reflects which underlying stage_key the deal is on.
How long it's been waiting.
The proposal's expiry date.
Which auto-nudge step the deal is on.
Source: app/dash_js.py:1140-1146 · app/deal_fsm.py:38-39
Both parties signed. Waiting on the deposit or full payment to arrive.
▸ Ways in
contract_signed stage_key
Both signatures land on DocuSeal.
from Sign FUP
real stage · key contract_signedWhere payment is chased
Ways out ▸
Deposit / full payment matched
±2% or 2000 HUF tolerance. deal_fsm.py:42.
Signed, unpaid.
Manual reassign.
"SIGNED — AWAITING PAYMENT".
Red, appears once past the due date.
Source: app/dash_js.py:1147-1150 · app/deal_fsm.py:39,42
Money's in. Waiting to kick off the build.
▸ Ways in
deposit_paid / full_paid stage_key
Stripe/bank match confirms payment.
from Proposal Signed
real stage groupWhere the build kicks off
Ways out ▸
Build stage_keys progress
waiting_dev etc. deal_fsm.py:43.
Paid, pre-build.
Manual reassign.
Confirms the match.
Adds a Main-Tasks entry for the build kickoff.
Source: app/dash_js.py:1151-1154 · app/deal_fsm.py:42-43
The site is being built. Ends when the retainer starts.
▸ Ways in
Build stage_keys
waiting_dev / website_built / final_paid / handed_over.
from Payment Arrived
real stage groupWhere the site gets built
Ways out ▸
Start marketing retainer
dealMarketing. dash_js.py:1938-1940.
Build in progress.
Manual reassign.
Shows once the build payment is confirmed.
Marks the invoice step done — bookkeeping only.
dealMarketing — advances to Ongoing marketing.
Adds a Main-Tasks entry.
Source: app/dash_js.py:1155-1162,1938-1940
Retainer client. Terminal on the board — no further column to move to.
▸ Ways in
Start marketing retainer
marketing stage_key / dealMarketing click.
from 🏗 Ongoing build
real stage · key marketingWhere retainer clients live
Ways out ▸
Terminal
No further column — the retainer is the end state on the board.
Retainer running.
Manual reassign — the only way out.
Adds a Main-Tasks entry (monthly retainer deliverable).
Source: app/dash_js.py:1163-1166
Silence won. Auto-reactivates the instant the lead writes back.
▸ Ways in
Negative-FUP ghost sweep
2 days silence after the final nudge. flows_calls.py.
from Contacted — No Appt.
Unreachable timeout
No contact channel responds within the window.
from Contacted — No Appt.
real stage · key ghostWhere silence goes
Ways out ▸
Any reply
G8 reactivation — any reply resurfaces it.
Silent, waiting for a reply.
14 napja nem válaszolt. Ha ír, automatikusan visszakerül a New lead oszlopba.
Manual reassign.
Static state marker.
Explains the auto-reactivation rule to the rep.
Adds a manual follow-up reminder outside the sequence system.
Source: app/dash_js.py:1167-1170
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.
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 onlyEBO.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?
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.
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.
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.