Architecture · ClientsFlow Pipeline
How two giant files became many small ones without changing a single behaviour · live build v098 · 2026-06-24
Imagine a workshop where one enormous binder held the instructions for everything: taking sales calls, booking meetings, sending invoices, replying to emails, drawing the proposal documents — all jammed into the same 4,780 pages. To change one thing you had to leaf through the whole binder, and one slip could affect anything. That's a "god-module."
We split that binder into labelled chapter-books — one for emails, one for money, one for calls, one for the documents — and did the same for the control-panel binder. Two things made this safe:
① We kept the same front desk. Everyone who used to "ask the binder" still asks the exact same desk and gets the exact same answer — they never learn a chapter moved. ② The chapters keep an internal phone line back to the shared services (logging, saving, sending) so they work exactly as before, and our automated safety checks still pass untouched. Those two clean joins are what engineers call "seams."
Each core file kept its name and its "front door." The coloured boxes are the new chapter-files the work was lifted into. Read the two arrow styles with the key below each diagram.
The engine: flows.py 4,780 → 2,053 lines; four domain chapters lifted out behind one unchanged front door.
The control panel: dash.py 3,098 → 2,417 lines; the styling and the heaviest wizard lifted into siblings, same rendered page.
Every function below was lifted out byte-for-byte and grouped by job. These are the real
names in the code today — nothing was rewritten, only relocated. (Leading _ = internal helper.)
.replace("/*CSS*/", CSS) — proven byte-identical to the original inline block.A seam is a clean join where two pieces meet without leaking into each other. The split rests on exactly two.
re-export facadeflows.py / dash.py by name. Those files now quietly forward each name to whichever chapter holds it — so no caller had to change a single line.from app.flows_x import …), so flows.handle_missive_incoming still resolves even though the body lives in flows_inbound.py.live shimsdef log(*a,**k): return _f.log(*a,**k) — resolved at call-time, so test monkeypatches (flows.log = fake) and live state both propagate. Moved bodies stay byte-identical."Module" is what each file holds; "interface" is how they're allowed to touch. The split defines exactly three contracts — and every one of them is what keeps behaviour identical.
Each chapter's functions become callable as flows.<name> again through a re-export
list at the bottom of flows.py. That list is the public interface — 71 names across four
chapters — so no outside caller can tell anything moved.
The chapters feed names up into one front door; callers only ever see flows.<name>.
Going the other way, each chapter depends on a small set of core services through live shims. This
matrix is that contract: the rows are the shared helpers in flows.py, the columns are the
chapters that call them. A change to one of these helpers ripples to every ✓ — which is exactly why they stayed
in the core rather than being duplicated.
| Core helper (stays in flows.py) | forms | billing | inbound | calls | does |
|---|---|---|---|---|---|
store() | ✓ | ✓ | ✓ | ✓ | read/write the deal store |
_c() | · | ✓ | ✓ | ✓ | get an API client |
log() | · | ✓ | ✓ | ✓ | activity-log a touchpoint |
stamp() | · | ✓ | ✓ | ✓ | timestamp a deal field |
_sync_stage() | · | ✓ | ✓ | ✓ | move a deal's pipeline stage |
_send_missive_email() | · | ✓ | ✓ | · | send an email (human-gated) |
ensure_ghl() | · | · | ✓ | ✓ | ensure the GHL contact exists |
lead_booking_link() | · | · | ✓ | ✓ | build the booking URL |
_deal_from_crm() | · | · | ✓ | ✓ | hydrate a deal from the CRM |
check_token() · _text_to_html() | ✓ | · | · | · | auth + email HTML for renderers |
_create_booking_meet_event() · create_sales_prep_task() | · | · | · | ✓ | kept in core (also patched by tests) |
Abbreviated to the shared helpers; each chapter also forwards a few of its own. The full per-file shim lists live in each module's header docstring.
The Control Panel assembles at startup through one entry point. dash.register(api)
builds the two security guards, then hands the same (api, guard, owner_guard) contract to every
panel; the extracted wizard rides in via the re-export so its routes register unchanged.
One contract — (api, guard, owner_guard) — composes every panel; the moved wizard registers through it unchanged.
| File | What lives there | Lines | Role |
|---|---|---|---|
flows.py | Shared helpers + the front door | 2,053 (was 4,780) | core |
flows_forms.py | Proposal / review HTML renderers | 332 | chapter |
flows_billing.py | Stripe · DocuSeal · Wise · Számlázz · payment chases | 603 | chapter |
flows_inbound.py | Missive + Fireflies webhooks & sweeps | 965 | chapter |
flows_calls.py | Calendar · Meet · booking · no-show · call audio · post-call | 924 | chapter |
dash.py | Control Panel shell + route registry | 2,417 (was 3,098) | core |
dash_styles.py | The Control Panel's entire CSS look | 462 | chapter |
dash_actions.py | Post-call wizard's multi-step commit logic | 264 | chapter |
/dash page serves CSS that is byte-identical to the source (42,469 chars matched).v098 and re-checked live.A5 · C4 · D5 · G1 passed — layout intact and the human-approval gate (nothing sends without a person) untouched.ADR-0010.main and has not been pushed
to the shared remote yet.