ClientsFlow · Email-System Overhaul · W4 · EBO
DRAFT for owner comments · 2026-07-12. The target speed experience after W4 ships — every click in the Control Panel and on the public booking page feels like Notion/ClickUp: instant acknowledgment, fast settle, no mysterious hangs, no "did my edit actually save?" doubt. Pure latency/caching/plumbing — no new features, no visual redesign (that is W8), no send-gating changes (W1). Assembled from the W4_SPEED handoff + the ANSWERS_ROUND3 rulings (Q3, Q23, and the carried always-warm approval). Scenarios W4-1 … W4-8.
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Mátyás opens the Pipeline board tab (normal day, board recently served or kept warm) | The full board — all columns, all cards — is painted and interactive in under 2 seconds from click to usable board | Copy: unchanged · Look: unchanged (no redesign — W8's job) · Where: Pipeline tab | The board response is served from the warm cache; the payload's own timing fields show a near-zero build (build_ms≈0) — served, not rebuilt |
Must NOT take 16–20s as historically reported; must NOT show a blank/"Loading…" board for more than 2s on a warm open; must NOT silently rebuild the whole store on every open | — | Critique: "Under 2s" is the invariant, but nothing on screen tells him whether a slow paint was the app or his Wi-Fi — so every slow moment erodes trust in the whole system. Suggestion: None needed on the UI (no new elements per the style rules); the timing fields the endpoint already returns are the audit trail — QA reads them, the screen stays clean. |
| 2 | QA (not Mátyás) re-measures the board build 3× with the cache deliberately bypassed, before and after W4 | A logged before/after number for the full board build, using the timing fields the endpoint already returns (build_ms / rows_ms / queue_ms / project_ms) — proving the 2026-07-11 store-read fix (~100s → ~9s full-store scan) actually reached the board path |
Copy: — · Look: — · Where: QA evidence log, not the UI | 3 fresh (cache-bypassed) board builds are timed on prod; the deal-store read portion (rows_ms) is compared against the pre-fix ~10s baseline |
Must NOT accept rows_ms > 3s for the current deal count without opening a follow-up (the handoff's explicit acceptance bar); must NOT add new instrumentation — the fields already exist, read them |
— | Critique: A one-time measurement decays — deals grow, and next quarter nobody knows if the board slid back to 8s. Suggestion: Since the endpoint already self-reports its timings on every build, have W0's existing health surface keep the latest board build_ms visible there (no new UI in the board itself) — a slow creep becomes a glance, not an archaeology dig. ❓ owner: worth asking W0 for? |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Mátyás opens the board after 30+ minutes away | Same experience as W4-1: the board paints in under 2 seconds — because a background keep-warm step has been quietly rebuilding the board cache on a fixed interval, exactly like the Today tab already gets | Copy: unchanged · Look: unchanged · Where: Pipeline tab | The existing 3-minute background cron gains a warm_board step mirroring the existing warm_today one — implemented by W0 inside its single-writer edit of that cron; W4 files the request and verifies. The cron's heartbeat log shows warm_board: {"status": 200} every run |
Must NOT have W4 edit the cron function itself (W0-exclusive seam); must NOT leave the board as the only major tab without a warmer; must NOT let the warmer mask a genuinely broken build (a failing warm step must show in the cron heartbeat, not silently vanish) | — | Critique: The warmer runs every 3 minutes but the board cache lives 120s — that leaves a recurring ~60s window each cycle where the cache is expired and a click still pays the cold rebuild. Warm "most of the time" is not the Notion feel. Suggestion: Close the gap on the cache side: lengthen the board cache's life to ≥ 240s (as the Today tab already has) so the 3-min warmer always lands inside a live window — no extra cron slot, no extra cost. ❓ owner sign-off: proposed threshold, not in the handoff. |
| 2 | Worst case: Mátyás hits the board at the exact moment the cache is genuinely cold (warmer missed a beat, fresh deploy) | The board still arrives within a documented, measured bound — proposed: ≤ 5 seconds click-to-board — never "however long it happens to take", and never a spinner of unknown duration | Copy: unchanged · Look: the normal loading state, briefly · Where: Pipeline tab | A cold rebuild runs synchronously but on the already-fixed one-pass store read (W4-1 step 2's measurement is the proof this bound is honest) | Must NOT exceed the documented bound without it being treated as a defect; must NOT regress to the per-deal read storm (one network call per deal) that caused the historic 16–20s reads | — | Critique: The handoff mandates "a documented, measured bound" but names no number — someone has to pick one or "bounded" means nothing. Suggestion: I propose ≤ 5s cold, ≤ 2s warm as the two-tier bar (cold should be rare enough after the warmer that 5s is acceptable). ❓ owner sign-off: these exact numbers. |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Mátyás makes any change (logs a call, moves a card's stage) and then views the board — same tab, other tab, or a second browser window | The board he sees already includes his change — every time, on the first look, with no manual refresh ritual and no "wait two minutes" | Copy: the changed card shows its new state · Look: unchanged · Where: Pipeline board | The canonical deal store was written synchronously at the click (that already works today); the fix guarantees no server copy keeps serving a pre-edit cached board afterwards | Must NOT ever show him a board that is missing an edit he made more than 2 seconds ago; must NOT train him to distrust the board ("better refresh twice to be sure") | The logged call / stage move writes its history row exactly as today (owned by W2's rules) — W4 only guarantees the board showing it is never stale. | Critique: This bug is invisible in single-server moments and bites exactly when the system is busiest — the worst possible trust profile. Suggestion: Whatever fix ships, QA must reproduce the stale case first (two concurrent server copies, edit on one, read on the other) and then prove it gone — a fix verified only on one server copy proves nothing. |
| 2 | QA measures first, before building: does this app ever actually run more than one concurrent server copy under Mátyás's real single-operator traffic? | A recorded verdict with evidence, one of two: (a) yes — multiple copies observed serving within a 2-minute window → the cross-copy invalidation fix is built; (b) no — traffic never triggers a second copy → the item is marked "observed non-issue, revisit if traffic grows" and no distributed-cache machinery is built on spec | Copy: — · Look: — · Where: QA evidence log | Live evidence (distinguishable server-copy identity in logs, or a controlled burst test) decides build vs. defer — the handoff's explicit measure-first rule | Must NOT build a distributed cache-invalidation mechanism blind; must NOT mark the item closed with neither evidence nor fix | — | Critique: Round 3's Q3=A (true-push board) changes this item's stakes: even if today's traffic never spawns a second server copy, W8's push channel plus warm containers may — deferring on today's evidence could plant a bug in W8's foundation. Suggestion: If verdict is (b), don't just defer — hand W8 a written constraint ("push board must carry the data in the push, or bust the cache version on every push") so the non-fix is a documented dependency, not a landmine. ❓ owner: agree measure-first stands, with this W8 rider? |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Mátyás clicks Log Call (with his edits to the confirmation email in place) and confirms | The button acknowledges in < 300 ms, the panel completes and frees him in ≤ 2 s — the response no longer waits for the Notion CRM page rebuild. Everything he entered is saved the moment the click lands | Copy: unchanged (no new toasts, no ceremony) · Look: unchanged, just fast · Where: the card's Log-Call panel | The canonical deal-store write and the calendar/Meet work happen exactly as today; only the Notion mirror rebuild is handed to the existing background worker pool instead of blocking the response — the pattern the codebase already uses elsewhere, applied consistently | Must NOT hold the click hostage to Notion's response time; must NOT change what gets written — same call touchpoint, same edited email body driving the calendar description (the shipped Ferenc-Zemen behavior), just asynchronous mirroring | The call touchpoint row appears exactly as today, with the edited copy — unchanged content, owned by W2. QA probe: the row is present and correct even though the click returned before the Notion mirror finished. | Critique: Making the click faster fixes the latency half of his complaint; the "did it save?" feeling also has a UI-feedback half — but that half is explicitly W7's (UI Action Discipline), and inventing a confirmation surface here would double-build it. Suggestion: Keep W4 surgical; note the seam so W7's feedback work lands on the now-fast endpoint. No new UI here. |
| 2 | Nothing — a rare failure: the background Notion mirror write fails after the click already returned | Mátyás's board and the deal's real state are still correct — the canonical store was written synchronously at the click. Only the Notion CRM page is temporarily stale, and the failure is logged (visible via W0's existing health/alarm surfaces, not a new W4 UI) | Copy: — · Look: — · Where: behind the scenes | Deal store (canonical) written synchronously; the deferred mirror write failed and logged — matching the codebase's already-documented invariant: "the deal-store write stays synchronous; only the eventually-consistent mirror is deferred" | Must NOT lose the logged call itself when only the mirror fails; must NOT report the click as failed when the canonical write succeeded; must NOT invent a new failure UI (existing logging/alarm surfaces only) | The touchpoint row exists (canonical); only the Notion page presentation lags until the next successful sync. | Critique: "Eventually consistent" needs an eventually — with no bound, a stale Notion page could quietly persist for days. Suggestion: Proposed bound: the Notion mirror reflects the change within ~10 s normally, and a failed mirror write is retried on the next regular sync pass rather than dropped. ❓ owner sign-off: the 10s expectation (not in the handoff). |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | The lead picks a slot and submits the booking form (normal day, Google Calendar healthy) | Existing flow, verify unchanged: the confirmation screen renders in ≤ 2 s; the calendar invite, Meet link, notification email, and the card's update all complete in the background moments later | Copy: unchanged (client-facing Hungarian confirm copy as today) · Look: unchanged · Where: public booking page | The already-shipped optimistic path: slot claimed + availability checked synchronously, everything heavy (CRM, Meet, email, board push) finalized on a background thread — the booking rule file's < 2s target, already honored in code | Must NOT regress the ≤ 2s confirm while touching this path; must NOT double-book (the claim + pre-filter protection stays exactly as is) | The booking lands in the lead's history exactly as today (owned by W2) — booked-call touchpoint + confirmation email record, written by the background finalize. | Critique: None — this path is already right; the EBO's job is to prove it stays right after the adjacent fix. Suggestion: — (no change proposed; verification only). |
| 2 | The lead submits while Google Calendar's availability service is slow or hung (the historic 60–90s scenario) | The confirmation screen still renders — worst case within ~9 s (the same 8-second wait cap the page's first availability check already uses, plus overhead) — never a 60–90s white-screen hang | Copy: the normal confirm screen (the lead never sees the degradation) · Look: unchanged · Where: public booking page | The second, authoritative availability check — today the only unbounded Google Calendar call on the critical path — gets the identical 8s wait-cap treatment its sibling pre-filter already has, failing open (proceed + log) on timeout. Only the wait is bounded; the fail-open semantics do not change | Must NOT hang past ~9s no matter what Google does; must NOT change the double-book risk posture (on timeout, protection still comes from the slot claim + the pre-filter — the same accepted window that exists today); must NOT fail the booking outright because an availability read timed out | Unchanged — the booking's history rows are written by the background finalize whether or not the availability check timed out (the timeout is logged internally, not shown to the lead). | Critique: ~9s is 4.5× the 2s invariant — acceptable only because it's a rare degraded mode, but a lead staring at 9s of nothing may resubmit. Suggestion: The slot-claim already makes a duplicate submit harmless (idempotent), which is the real protection; any visible "still working…" affordance is W7 UI-discipline territory, not W4's. ❓ owner sign-off: ~9s as the accepted degraded bound. |
| 3 | QA proves the bound: simulate a hung availability check on staging (test calendar), book a ZZ-sentinel slot | The confirm screen renders within the ~2–9s window instead of hanging; a test asserting "availability check hangs > 8s → booking still returns within ~9s" is green; test data purged after | Copy: — · Look: — · Where: staging + test suite | A unit/integration test mocks the availability call to hang and asserts the booking handler returns within ~9s; a live staging probe with a ZZ-sentinel lead confirms it end-to-end | Must NOT ship the fix on a green typecheck alone (the hang only shows under a hung dependency — it must be simulated); must NOT leave ZZ test bookings behind | ZZ-sentinel rows only, purged after the pass. | Critique: — Suggestion: — (pure verification row). |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Nothing — a payment lands (Stripe webhook or bank-transfer match fires) | On the board, the deal shows its payment progress exactly as today; behind the scenes the webhook response returns promptly instead of waiting out a full Notion page rebuild | Copy: unchanged · Look: unchanged · Where: the paying deal's card (eventually-consistent Notion page follows) | The two payment-path Notion mirror writes move to the same background-worker pattern as W4-4; canonical payment state still written synchronously. All existing payment tests stay green | Must NOT change any payment logic, amounts, or stage transitions — timing only; must NOT let a webhook exceed the provider's timeout because of a Notion hang (the retry storm that follows is worse than the slowness) | Payment history rows unchanged in content and order (owned by W2/W1) — only the Notion mirroring moment shifts by seconds. | Critique: Bundling these two low-priority wraps into the same change as W4-4 inflates the diff and the QA surface for near-zero user-visible gain. Suggestion: The handoff already allows it: bundle only if the diff stays small, otherwise ship as a fast follow-up after W4-4 proves the pattern. Keep the option, don't force it. |
| 2 | QA traces (before any edit) the inbound-reply mirror write: does it run on the already-asynchronous fast-ack path, or synchronously inside the webhook response? | A one-line recorded finding with the code trace: either "already off-thread — no fix needed" or "still synchronous — wrap it like W4-4"; only in the second case is the code touched | Copy: — · Look: — · Where: implementation notes / QA evidence | The inbound email webhook is documented as fast-ack (it spawns background processing immediately) — if the mirror write lives inside that spawned worker, it never blocked anyone and must be left alone | Must NOT "fix" a call site that was never on a request thread (churn without benefit, plus merge-collision risk with W1/W2 who also touch inbound); must NOT skip the trace and guess | — | Critique: This row is the handoff's own discipline made visible: measure/trace first, edit second — the cheapest bug is the one you don't introduce. Suggestion: — (keep as-is; it's the pattern W4-3 step 2 uses too). |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Mátyás drags/moves a card to another stage | The card visually moves at once (feedback < 300 ms — the existing optimistic UI) and the move is fully settled server-side in ≤ 2 s; the board never snaps the card back on a healthy save | Copy: unchanged · Look: unchanged (existing optimistic move) · Where: board columns | Optimistic UI applies the move instantly; the canonical store write settles behind it — existing behavior, now verified against the written invariant | Must NOT block the drag on the server round-trip; must NOT leave the UI and store disagreeing after 2s | Stage-move history behavior unchanged (owned by existing stage-sync rules). | Critique: "< 300 ms feedback" is the handoff's number; for already-optimistic paths like this drag the real bar is one frame (< 100 ms) — 300 ms of nothing after a drag-drop feels broken even though it passes. Suggestion: Two-tier reading: optimistic paths (drag, button-press states) ≈ instant (< 100 ms); request-bound paths ≤ 300 ms to first feedback. ❓ owner sign-off: adopt the two-tier bar. |
| 2 | Mátyás opens the Send-Emails modal on a card | The modal opens with feedback < 300 ms and is fully usable in ≤ 2 s (this modal historically took 38 s; that fix already shipped — existing flow, verify unchanged, guarded against regression here) | Copy: unchanged · Look: unchanged · Where: Send-Emails modal | No new work — the shipped fix is re-timed as part of this sweep so a regression can't ride in silently under W1/W2's concurrent edits to the same surfaces | Must NOT regress toward the historic 38s open while W1/W2 rework the modal's contents; must NOT re-fix what is already fixed (verification only) | — | Critique: — Suggestion: — (regression guard row; no change proposed). |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | QA checks the Modal spend after the keep-warm + warm-container settings have been live (projected to a full month) | A recorded number: projected monthly hosting spend with W4's changes, and total hosting + AI ≤ $30/month. If the projection breaches the cap, the warm sizing is dialed back (shorter warm window / fewer warm containers) until it fits — the ≤ 2s warm-open target is then re-verified at the cheaper setting | Copy: — · Look: — · Where: QA evidence + W0's billing-guard surface | The keep-warm step costs ~one extra internal request per 3-min cron tick (near-zero); the always-warm container floor is the real line item — its projected cost is read from Modal's own usage/billing view, not estimated | Must NOT ship warm-container sizing without a cost projection; must NOT let W4 spend eat the AI budget's share of the $30 (the cap is TOTAL, and the ongoing spend alarm itself is W0's — W4 only proves its own contribution fits) | — | Critique: "Pay for it" (round 2) and "≤ $30 total" (round 3) can genuinely collide if AI usage grows — and W4 would be the silent culprit nobody suspects. Suggestion: Record the chosen warm sizing + its measured monthly cost in one line in the W4 close-out notes, so any future budget alarm can be traced to (or cleared of) this decision in seconds. |
Each build work item from the W4_SPEED handoff, and the scenario steps that prove it works when clicked through / probed on staging then prod.
| Work item | What it delivers | Proven by (scenario · step) |
|---|---|---|
| WI-1 · Board build re-measured (handoff W4-1) | Live before/after timing of the full board build using the endpoint's own timing fields; confirms the 2026-07-11 store-read fix reached the board path; follow-up opened if the store-read portion still > 3s. | W4-1·1 · W4-1·2 · W4-2·2 |
| WI-2 · Board keep-warm (handoff W4-2, W0 implements) | A warm_board step in the existing 3-min cron (filed to W0, single-writer seam respected); cron heartbeat shows warm_board: 200; cold opens become rare. | W4-2·1 (heartbeat + warm-open timing) |
| WI-3 · Bounded booking availability check (handoff W4-3) | The last unbounded Google Calendar call on the booking critical path gets the existing 8s wait-cap, fail-open semantics unchanged; the 60–90s hang class is extinct. | W4-5·1 · W4-5·2 · W4-5·3 |
| WI-4 · Log-Call mirror write off-thread (handoff W4-4) | Log Call responds in ≤ 2s; Notion mirror deferred to the existing background worker; canonical write stays synchronous; existing Log-Call tests green. | W4-4·1 · W4-4·2 · (visibility) W4-3·1 |
| WI-5 · Billing mirror writes off-thread (handoff W4-5) | Both payment-path Notion writes wrapped the same way (bundled only if the diff stays small, else fast follow-up); billing tests green. | W4-6·1 |
| WI-6 · Inbound mirror call traced first (handoff W4-6) | A recorded one-line finding with the code trace — "already off-thread" or "wrap it" — before any edit to the inbound path. | W4-6·2 |
| WI-7 · Cache-coherence: measure, then fix or defer (handoff W4-7) | Evidence-based verdict on multi-copy staleness; fix built only on evidence (or explicitly deferred with a written W8 push-board rider); either way the "own change invisible" experience ends. | W4-3·1 · W4-3·2 |
| WI-8 · Dash click-invariant sweep (mission-level) | The booking-only < 300 ms / ≤ 2s invariant written down for the dash and swept: stage moves, Send-Emails modal open (regression-guarded), board opens. | W4-7·1 · W4-7·2 · W4-1·1 |
| WI-9 · Cost fit under the $30 cap (Q23 rider) | Projected monthly hosting spend recorded for the chosen warm sizing; total hosting + AI ≤ $30/month or the sizing is dialed back and speed re-verified. | W4-8·1 |