ClientsFlow · Email-System Overhaul · W5 · EBO
DRAFT for owner comments · 2026-07-12. W5 is a verification-only workstream: features that were built + QA'd in past sessions but ended up stranded, reverted, or only claimed done must now be settled live — clicked on the real board, not just found in the code. Every scenario below is the exact click-path that proves one stranded candidate works today (or records, in the open, that it doesn't and who owns the fix). W5 builds nothing, merges nothing, deploys nothing. Scenarios W5-1 … W5-8, covering findings F3 · F6 · F7(a/b/c) · F8 · F10.
ZZ… sentinel deals, and everything created is purged afterwards (W5-8). Read-only checks (token GET, looking at a card) may touch prod. Q27=A overrides the handoff's "owner-decide-priority" on the proposal workbench: the owner has now ruled it IN — a one-page inline proposal workbench (edit any text/price inline, one button sends + schedules follow-ups). That build belongs to W2's scope; W5 only confirms live that nothing of it exists today, so W2 starts from a clean, honest baseline. Not stranded, no recovery: F8 (Fireflies auto-dispatch) already has an active owner (plans/meet-recorder-autojoin-5s/) — W5 runs one confirm-not-regress check and stays out of its way.ZZ sentinel and is purged at the end; Notion cleanup is archive-only, never permanent delete; nothing is ever sent to a real lead; each verdict lands as an explicit ✅ CONFIRMED LIVE / ❌ GAP CONFIRMED + owner, recorded as it happens (never batched in memory); all verification uses the current dash token — never a locally computed one (the local secret is stale and produces a token that 403s).| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Open the live dash URL with the current known token (…/dash?token=57723d9da370c5ff3afcd52e) in a browser |
The Control Panel loads with a 200 and the pipeline board renders with real cards — proof the token is valid and live verification can proceed | Copy: normal dash chrome + board columns · Look: full board paint, no error page · Where: production dash (read-only look, nothing clicked yet) | Nothing — a read-only GET. The token's validity is recorded in the W5 findings log before proceeding | Must NOT proceed to any other W5 scenario on a 403; must NOT fall back to a locally computed token (known stale — it produces the old 6e90… value and always 403s) |
— | Critique: A silent 403 was exactly what stranded the last pass — the failure looked like "feature can't be checked" instead of "door is locked". Suggestion: Log the gate result (200/403 + timestamp) as the first line of the W5 findings file, so any future re-run sees instantly whether access — not the feature — was the blocker. |
| 2 | If step 1 returned 403: fetch the current dash secret from the clientsflow Modal secret store and retry with the freshly derived token | The dash loads with 200 on the retry — the rotated token is now the recorded working token for the rest of the run | Copy: same dash · Look: same board · Where: production dash | The new token replaces the stale one in the findings log; all later scenarios use it | Must NOT ask the owner for the token before trying the secret store; must NOT continue the run on the stale token "just in case" | — | — |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | On the staging board, create/advance a ZZ sentinel deal to the awaiting-signature (post-proposal) stage and open its card | The card shows two distinct signing affordances: the client's signing link AND a separate "My signing link" button for the contractor's own signature | Copy: "My signing link" (rep/contractor side), clearly distinct from the client link's label · Look: a button on the awaiting-signature card · Where: the ZZ card, signature-stage panel | The card payload carries both signer URLs (client + rep) from the DocuSeal submission — existing shipped wiring, nothing new is created by looking | Must NOT show only the client link (that was the original stranded-claim bug); must NOT label the two links so similarly they're mistaken for each other | — (opening a card writes nothing) | Critique: Two links on one card differ only by their label — a rushed click on the wrong one puts the owner into the client's signer session. Suggestion: If a mis-click proves easy during this verification, note it for W3/W7 (Details-panel owners) as "visually separate the two signer links" — W5 records the observation, does not restyle anything. |
| 2 | Click "My signing link" | A DocuSeal signer page opens scoped to the contractor's own signature fields on the ZZ deal's combined document — not the client's fields, not someone else's submission | Copy: DocuSeal signer form showing the contractor party's fields · Look: the signing surface · Where: new tab, DocuSeal | Nothing in the pipeline — the click only opens the already-issued signer URL. Verdict recorded: F7(c) ✅ CONFIRMED LIVE (or ❌ with the exact wrong behavior seen) | Must NOT open the client's signer session; must NOT 404 / open an expired submission; must NOT sign anything for real during verification (do not submit the form) | — | — |
| 3 | While the signer page from step 2 is open: attempt to place a typed signature in a signature field (this is F7(a), verified opportunistically here — same open document, zero extra setup) | The signature field only accepts a drawn signature — the typed-text option is rejected/absent on this submission | Copy: DocuSeal's signature widget without a usable "type" mode · Look: draw-only signature input · Where: the open DocuSeal form | Nothing — behavior check only. Verdict recorded: F7(a) ✅ CONFIRMED LIVE (the per-submission draw-only setting is already confirmed on main in code; this is the one live confirmation) | Must NOT accept a typed signature; must NOT complete/submit the signature — abandon the form after checking | — | Critique: None — piggybacking F7(a) onto the F7(c) click keeps the run cheap; a separate scenario would be ceremony. |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Complete both signatures on the ZZ staging deal's document (or replay the "both signed" webhook / trigger the existing DocuSeal backup-poll reconciliation for it) | Within the normal processing window, the ZZ card leaves the awaiting-signature state — the signed-contract flow has fired | Copy: card's stage/status advances per the existing flow · Look: board reflects the post-signing state · Where: the ZZ card on staging | The existing post-signing flow runs: signed document stored, payment link ensured, the client email composed and sent — all pre-existing behavior, W5 only triggers it on a sentinel deal | Must NOT run this against a real lead's deal; must NOT run it on prod (staging + ZZ only) | The signing event and the outgoing email land in the ZZ card's history per the existing rules — see step 2 for the exact check. | — |
| 2 | Open the ZZ deal's inbox (the sentinel email address) and count the client-facing emails from this signing event | Exactly ONE client email, containing all three payment-relevant pieces in one body: the signed-contract download link, the Stripe payment link, and the manual bank-transfer details with the exact amount | Copy: one email, three payloads (Hungarian client copy, e.g. the signed-doc link + fizetési link + átutalási adatok) · Look: a single message in the thread · Where: ZZ inbox | Verdict recorded: F10 ✅ CONFIRMED LIVE if one-email-with-all-three; ❌ GAP if a second client email arrived or a payload is missing | Must NOT find two separate client emails (the original bug); must NOT find the Stripe link or the amount missing from the single email; must NOT count the internal rep-copy notification as a client email (it is a separate, clearly-labeled internal message — expected, not a regression) | EXISTING ENTRY — verify parity The ZZ card's history shows the sent client email as one outbound touchpoint whose stored copy matches the email in the inbox word-for-word (the 100% history↔sent parity rule, Q7). |
Critique: "One email" can silently regress again — this exact flow already drifted once from what the owner believed was live. Suggestion: Hand W6 (QA gate) this scenario as a permanent regression check: any future change touching the post-signing flow must re-run this one-email count before shipping. |
| 3 | Check the internal notification side of the same event | One clearly-labeled internal rep-copy notification exists, separate from the client email — so the owner knows the contract closed without the client seeing a second message | Copy: the internal notification's label marks it internal · Look: internal channel/inbox, not the client thread · Where: owner-side surfaces | Nothing new — confirming the shipped design (client email + separate internal copy) is intact as a pair | Must NOT find the internal copy addressed to the client; must NOT find it missing entirely (the owner would be blind to closings) | — | — |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Run the existing frozen-set classifier eval against the current live classify prompt and record the false-negative rate (positive leads classified negative) | A concrete number: "X of N frozen positive-lead replies were classified negative" — plus the comparison to whatever pre-fix baseline can be reconstructed from the June session logs | Copy: the eval's printed result · Look: terminal/eval output, written into the W5 findings log · Where: offline eval run (Eval-Lab machinery) | Read-only evaluation over the frozen labelled set; the live prompt is not modified; the result + timestamp is recorded as F6's evidence | Must NOT modify or "quickly improve" the live prompt during the run (W5 is verify-only); must NOT let the eval send anything or touch lead data; must NOT report a vibes verdict without the number | — | Critique: Without a declared "good enough" line, the number lands and nobody knows if it closes F6 or reopens it. Suggestion: Owner sets the acceptance threshold when signing this EBO (see open decision D1) — e.g. "0 false negatives on the frozen set closes it; any >0 goes to the prompt-iteration loop." Then the verdict is mechanical, not a judgment call. |
| 2 | Apply the verdict per the recorded number | One of two recorded outcomes: CLOSE ✅ false-negative rate within the accepted threshold — F6 closed as already-good; or ROUTE rate above threshold — F6 handed to the Templates/prompt-iteration owner as a normal measured iteration loop (frozen set + metric already exist), with the eval result attached | Copy: the verdict line in the W5 findings log · Look: — · Where: findings log + the receiving workstream's queue if routed | Nothing in the product — a routing decision with evidence attached | Must NOT retune the classifier inside W5; must NOT close F6 without the eval number as evidence; must NOT let a ROUTE verdict silently die — it must land in a named owner's queue | — | — |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Start a live test Google Meet (ZZ-tagged test event, no real lead invited) and wait through one poll cycle | The recorder is dispatched to the Meet automatically, within the ~10-second polling cadence of the live-call signal — behaving exactly like clicking the manual dispatch button would (same dispatch, no title) | Copy: the recorder/notetaker joins the Meet · Look: notetaker participant appears in the call · Where: the test Meet + the dash's call surfaces | The existing merged auto-dispatch path fires on the live-call signal; nothing is configured or changed — pure observation. Verdict recorded: F8 behavior intact / regressed | Must NOT treat a missed signal outside the poll window as a new bug to fix here (that is the documented architectural ceiling, owned by the existing recorder plan); must NOT invite a real lead to the test call | The call's transcript/archive entries follow the existing call-layer rules once the recorder joins — W5 adds nothing to history itself. | Critique: One test call is a thin sample for a timing-sensitive feature — a lucky pass can mask a flaky window. Suggestion: If the single confirmation passes, still attach the dispatch timestamp delta (signal → join) to the findings log, so the recorder plan's owner accumulates real latency datapoints instead of pass/fail bits. |
| 2 | Record the hand-off in the findings log | F8 marked NOT STRANDED with a pointer: all further latency/reliability work belongs to the existing recorder plan bundle + its QA checks — not to this overhaul's workstreams | Copy: the routing line in the findings log · Look: — · Where: findings log | A recorded routing decision; prevents duplicate ownership of the same feature area | Must NOT leave F8 listed as an open recovery item after this run; must NOT scope any recorder work inside W5 or the overhaul | — | — |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | On the live dash, walk the current proposal path for the ZZ deal up to the proposal preview step and inspect every control on it | The current proposal preview: whatever inline editing exists today is noted exactly — and specifically no editable system/user prompt pair, no regenerate-with-edited-prompt control is found anywhere on the proposal surface | Copy: today's proposal-step controls, listed as found · Look: the existing preview screen · Where: proposal step on the dash | Nothing — observation only. Verdict recorded: F3 ❌ CONFIRMED NEVER BUILT (live look matches the code audit) | Must NOT "find" the workbench by generously interpreting an existing edit box as one (the claim is specific: editable prompts + regenerate + one send-and-schedule button); must NOT start building any part of it here | — | Critique: A screenshot beats a sentence for a baseline that another workstream will build against. Suggestion: Attach one screenshot of today's proposal preview to the findings log as W2's before-picture — the Q27 workbench acceptance can then be judged against a concrete baseline. |
| 2 | Record the routing: F3's rebuild = the Q27=A one-page inline workbench, owned by W2 | The findings log carries the supersede note explicitly: handoff said "owner-decide-priority"; round 3 decided — build it (Q27=A), in W2; W5 contributes the confirmed-clean baseline and nothing else | Copy: routing + supersede note · Look: — · Where: findings log + W2's queue | A routing decision with the round-3 anchor attached | Must NOT leave two contradictory verdicts alive (the handoff's "if still wanted" and Q27's "wanted") — the log must state round 3 wins; must NOT scope the workbench's design here | — | — |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Open the ZZ staging deal's Details panel, type an obviously malformed value into the tax-number field (e.g. not-a-tax-number) and save |
Today's expected (broken) behavior: the save is accepted silently — no format hint, no rejection, no flag. Seeing exactly this confirms the gap is real on the live app, not just in the code | Copy: the malformed value sits saved in the field · Look: no error state anywhere · Where: Details panel, tax-number field on the ZZ card | The malformed value is stored on the ZZ deal (sentinel data — purged in W5-8). Verdict recorded: F7(b) ❌ GAP CONFIRMED LIVE | Must NOT do this on a real lead's card; must NOT patch the validation in as part of this run (it is W7's named work item, sequenced after W3 — fixing it early would race W3's Details-panel rewrite) | — | Critique: The failure this guards is downstream and embarrassing — a client noticing their own tax number is garbled on a signed contract. Suggestion: W5's finding should state the acceptance for W7 in one line — "malformed tax number on save → rejected with a format hint (client-side) AND refused server-side" — so W7 inherits a test, not just a bug report. |
| 2 | Record the routing in the findings log | F7(b) marked GAP — ROUTED TO W7, with the note that it runs after W3 settles the Details-panel field schema | Copy: routing line · Look: — · Where: findings log | A routing decision; the malformed ZZ value stays flagged for the W5-8 purge | Must NOT leave the malformed test value on any deal after the run; must NOT let this gap end up owned by nobody (it was orphaned once already) | — | — |
| # | You do | You should see | Element that changes copy · look · where |
What changes underneath | Must NOT happen | 🕓 Touchpoint history | 💡 UX critique + suggestion |
|---|---|---|---|---|---|---|---|
| 1 | Purge all ZZ sentinel data created by this run: the staging deal(s), their scheduled/sent test emails, the test calendar event, and any DocuSeal test submission | Each purge step reports done; Notion-side removals are archive operations (reversible), never permanent deletes | Copy: — · Look: ZZ artifacts gone from their surfaces · Where: staging store, Notion (archived), inbox, calendar | Sentinel records removed/archived across every system the run touched; the findings log lists what was purged, item by item | Must NOT permanently delete anything in Notion (archive only — standing invariant); must NOT purge anything without the ZZ sentinel; must NOT skip a surface (a leftover ZZ email or event is a failed cleanup) | The ZZ card's history disappears with the archived card — no real lead's history is touched at any point. | — |
| 2 | Reload the staging board and sweep for leftovers | No ZZ card in any column; the findings log closes with the full verdict table (W5-1…W5-7 outcomes + owners) — the deliverable the next workstreams build on | Copy: clean board · Look: no sentinel cards · Where: staging board | Run closed; verdict table final | Must NOT close the run with any verdict still marked "pending"; must NOT report clean without the visual board sweep | — | Critique: Cleanup is where verification runs historically get sloppy — a leftover ZZ deal later pollutes metrics and once even got auto-emailed. Suggestion: Make the board-sweep screenshot (no ZZ cards visible) the last attachment of the findings log — cleanup proven the same way the features were: by pixels, not by a "purge succeeded" line. |
Each W5 verification work item (one per stranded-feature candidate), and the scenario steps that settle it live.
| Work item | What it delivers | Proven by (scenario · step) |
|---|---|---|
| WI-1 · Live-access gate | A validated dash token (retrying via the Modal secret store on 403) recorded before any verification — the blocker that stranded the previous pass can never silently repeat. | W5-1·1 · W5-1·2 |
| WI-2 · F7(c) rep sign-link, live | "My signing link" renders on the awaiting-signature card and opens the contractor-scoped DocuSeal signer — upgraded from wiring-confirmed to seen-live. | W5-2·1 · W5-2·2 |
| WI-3 · F7(a) draw-only signature, live | Typed signatures rejected on a live DocuSeal submission (piggybacked on the same open document — zero extra setup). | W5-2·3 |
| WI-4 · F10 one-email consolidation, live | Both-signed event → exactly ONE client email carrying signed-doc link + Stripe link + bank details, internal rep-copy separate, history↔sent parity verified. | W5-3·1 · W5-3·2 · W5-3·3 |
| WI-5 · F6 classifier eval + verdict | A measured false-negative rate on the frozen set vs baseline, then a mechanical CLOSE/ROUTE verdict against the owner-set threshold (D1). | W5-4·1 · W5-4·2 |
| WI-6 · F8 confirm-not-regress + hand-off | One live Meet confirms auto-dispatch within the ~10s cadence; all further recorder work routed to its existing plan owner. | W5-5·1 · W5-5·2 |
| WI-7 · F3 clean-baseline + Q27 routing | Live confirmation the proposal workbench was never built + the recorded supersede (round-3 Q27=A rules it IN, built by W2) with a before-screenshot baseline. | W5-6·1 · W5-6·2 |
| WI-8 · F7(b) validation gap + W7 routing | Live proof the tax-number field saves garbage silently, plus the one-line acceptance handed to W7 (sequenced after W3). | W5-7·1 · W5-7·2 |
| WI-9 · ZZ purge + closed verdict table | Every sentinel artifact archived/removed across all surfaces, a pixel-proven clean board, and the final F3/F6/F7/F8/F10 verdict table as W5's deliverable. | W5-8·1 · W5-8·2 |