> **The one-month build log.** 24 May → 22 Jun 2026.
> 15 commits · ~30K lines of real code · ~57K net lines added (incl. lockfile churn). One Next.js 16 CRM that took a legacy Django app and gave it AI, an email pipeline, and a real supplier map.
---
## The Project
**WFDASH** is a business CRM — customers, companies, suppliers, quotes, orders, driver collection pool, delivery notes, repairs. It's a re-platform of an older Django app, rebuilt on a modern stack:
> **North star:** *"One scheduler, one workflow, one source of truth — no long-running process in the Next.js app, no racy counters, no fabricated cost prices."*
---
## The Timeline At A Glance
```
24 May ─── Foundation sprint (UI kit, PWA, PocketBase collections)
25 May ─── PocketBase APIs + migration docs
27 May ─── Supplier map (Leaflet) ships
11 Jun ─── Test scaffolding (Vitest + Playwright + line endings)
16 Jun ─── Email-to-quote pipeline (v3.1 agentic + PDF text)
22 Jun ─── Big batch: atomic numbering → email cards → AI rewriter → pricing lock → UX polish
```
**By the numbers:**
- **15 commits**, ~57K net lines added (78K insertions / 22K deletions; ~27K of that is real code in `src/`)
- **29,582 lines** currently living in `src/`, `n8n_workfows/`, `scripts/`, `docs/`, `e2e/`
| `a6ac88a` | Node 20.9.0 enforced via `engines` + `nixpacks.toml` (so CI builds don't surprise us with Node 18) |
Why it matters: the warehouse team can see supplier locations at a glance, click any pin to fix a wrong coordinate, and the map works in dark mode in the cab at night.
---
## Phase 4 — Test Scaffolding (11 Jun)
**Commit `c4e1c62` — Standardize line endings + full test + config scaffolding**
**This is the headline feature.** Three workflow versions, one PocketBase schema, one atomic counter, and a long list of "no, really, it works this time" fixes.
### The flow
```
IMAP poll (quotes@wfsales.co.za, every 5 min, UNSEEN only)
↓
Parse Email + binary preservation
↓
Drop blacklists / auto-replies
↓
Is reply to existing quote?
├── yes → MCP: quotes.append_reply → terminal
└── no → Carry ctx → Split Binary → Extract from File (PDF)
PocketBase record + per-attachment rows linked back
```
### Commit `41b5fc4` — Pipeline v3.1 (agentic + PDF text)
Replaces the brittle v3 "Information Extractor → Code" pair with a **langchain AI Agent** that directly calls the MCP tools. PDF attachments go through `Split Binary → Extract from File (PDF) → Aggregate` so the LLM sees the line items as text. Flips `passthroughBinaryImages: false` so PNG attachments survive to the upload tool — the v3 bug where `attachment_1` was eaten by LLM vision is fixed.
**The bug:** in v4, by the time the workflow reached "Upload Attachment", the binary had already been stripped → silent upload failure.
**The fix (v5 "Catch and Create"):** upload attachments to PocketBase *immediately* after auth (binary still fresh), then do all the rest using record IDs, then PATCH each attachment to link it to the quote. The old node is renamed `DISABLED - Upload Attachment` so it's a one-click rollback.
- **Quote detail page** — claimer info with user icon
- **`DataTable`** — now accepts per-row custom class + style props (powers the color coding)
---
## Phase 7 — AI Quote Item Rewriter (22 Jun)
**Commit `2c6155a`**
The user pastes in a wall of supplier-speak ("1 x ABC pump model XYZ-9000 c/w 2" flanged inlet, 415V, IP55 rated...") and the rewriter turns it into clean, structured line items.
- **Expandable row previews** in the quotes list view
- **DataTable overhaul** — 567 lines changed to support row expansion, custom row styling, server-side state
---
## Phase 8 — Pricing Lock & Quote Polish (22 Jun)
**Commit `86c942a`**
The pricing story gets real:
- **Locked source-of-truth pricing controls** — once locked, cost/selling prices auto-calculate from the source. No more "two people edited the same cell, which wins?"
- Inline cost/selling inputs **removed** from the quote items table — they live in the locked pricing panel now
- **Quote item fields extended:** added `model` and `size`, removed old `type`
- **`/api/v1/quotes/items/rewrite` timeout bumped to 180s** — long AI batches need the headroom
- Up to 5 visible at once, 4s default duration, action buttons styled with proper tokens
- All 24+ toast call sites get the new look for free — zero migration
### Hide/show completed quote items
- Toggle in the "Quote Items" card header (outline by default, solid indigo when active — deliberately different from the amber Rewrite button and the green done state)
- Incomplete items float to the top, done items sink to the bottom
- **Row numbers stay stable** — done item #5 still says "5.", not "1."
- Celebratory row when everything is done + hidden: `All N items processed` + a Show-completed button
### Memoization
`displayItems` memo handles sort + filter together so the toggle is instant.
| 4 | Orders | partial/live | Status board + drv/collection states reflected in UI |
| 5 | Collection pool | partial/live | Assignment UI + filtered work queue |
| 6 | Delivery notes | not migrated | Generate delivery records from delivered/loaded order items |
| 7 | Repairs | not migrated | Rebuild only the useful request/order flow |
| 8 | Stock | intentionally folded | Separate `stock` collection later if needed |
Out of scope (explicit): `stock_orders`, `customer_portal`, `rep_portal`, `internal_stock`, `pos`, `ai_orders`, `system_monitor`. AI / email / monitoring all live in n8n and only talk to the app via API.
---
## The Decisions That Shaped This Build
A few calls we made that you might want to remember for the video:
1.**n8n over a Next.js worker.** Email polling + AI extraction runs in n8n, not in the Next.js process. The app stays stateless; n8n is the single scheduler. Reason: cold-start reliability + the AI Agent node already lives there.
2.**PocketBase over Postgres.** Single binary, no managed cloud, fits Coolify self-hosting. Cost = hosting only.
3.**MCP + REST side-by-side.** REST for humans/scripts (clean auth, clean error codes), MCP for n8n (one endpoint, `tools/list` + `tools/call`). Both go through the same `quotes-service.ts`.
4.**v5 "catch and create" for attachments.** Upload to PB first, link after. The binary is freshest at the top of the workflow; doing it later (when v4 tried) means the buffer is gone. Old node renamed `DISABLED - Upload Attachment` so rollback is one click.
5.**Locked pricing over free-form inputs.** Cost/selling prices live in a locked panel, not on the line item. No more "two people edited the same cell, which wins?"
6.**Vendor ID → stable color hash.** Claimed quotes are color-coded by claimant using a stable hash → Tailwind palette. No "Bob is teal today, indigo tomorrow" drift.
7.**Tests skip cleanly.** Vitest + Playwright both skip what they can't run (no API key → skip, no creds → skip). Fresh checkout gets `1 passed, N skipped`, never a red CI.