Design preview · Event Booking (TMPL-EVBK01) · 8-section structure · updated 2026-08-06

← Design HQ  ·  HTML on GitHub  ·  PRD — prd-as-event-booking.md

Event Booking — the flow, organized by journey

Event Booking — Discover, set it up, go live, then the guest thread.

SizePortrait scrolls 375 · sideways is a slide deckNotes
Design brief — overview · scope · journey (closed)

The Event Booking flow, every state, in journey order — the build contract; ACs cited by id, never restated. Companion: assistant-lifecycle.html (master) · on-brand-support.html (token/component source). This file is the canonical structural reference for per-assistant flow mockups (canon §“Assistant-flow mockup structure”).

8 sections: overview → scope → content → journey → desktop → mobile → components → future Discover → Evaluate → Activate → Monitor (founder) Trigger → Interact (forks on booking_surface) → Outcome → Fallback (guest) amber = pending/action-needed · emerald = fixed/healthy/resolved Flow / Components / Future (canon 2026-08-01)

Widths here sample a fluid continuum — content scales continuously; structure steps only at sm 640 · md 768 · lg 1024. Kit: design-canon.md U8.

Event Booking (TMPL-EVBK01) turns an event host's WhatsApp DMs into confirmed spots. A guest taps the founder's booking link — or just texts — and the assistant captures the RSVP/ticket request right in the chat (name, which event, headcount), relays the founder's own ticket link exactly as she gave it, and once payment is learned — her one-tap Mark paid — sends the Meta-approved confirmation and reminds the guest 1 day + 1 hour before it starts. Free RSVPs confirm the moment they're captured — no payment leg at all. $1.75 per confirmed booking; MystFlo relays the ticket link but never touches the money. Bookings live in MystFlo's own ledger and surface on Pulse (metrics, attention rows, the Attendees list); when the assistant can't answer, the founder takes over in the same WhatsApp thread — no second app.

In this build sections 4–7 = the Active contract
  • Full founder journey: Explore card → Overview → Personalize (375 + desktop split) → Connect → Try → Activating / Activated → Pulse + Attendees destination.
  • Two payment structures only (full_prepay · free_rsvp) — v1 is in_chat capture only, no own_form (that radio, and the roster-sheet mechanics that come with it, is Workshop Booking / Retreat Booking scope, not this card's).
  • Payment relay only: link or QR today (QR upload shipped #3153, 2026-09-09 — supersedes the #4159 / PR #4201 coming-soon state; nests under its option per #4701); link + QR together is specified-not-built (#4703, Peyton 2026-09-10 — shared engine, so drawn here INSIDE Personalize 02 · How guests pay exactly as in workshop-booking.html: a tick-one-or-both checkbox group, one QR-image bubble with the link in its caption when both are ticked). Confirmation on Mark paid (free RSVP confirms on capture); reminders 1 day + 1 hour before start (shared engine — Workshop card AC-WSBK-02/03 ↗).
  • Template-approval honesty states (sec-4 banner, sec-6 amber notice) and the rejected-template fix path (sec-6b — drawn as contract, generalized from the shared booking engine's rejected-template state, not independently verified for this card).
  • Handover in the same thread (WhatsApp Coexistence) + one-way Slack mirror (sec-12).
  • Attendees records destination (same records-layer contract every booking card gets — sec-13 is its build contract).
  • Several live events on one WhatsApp number is supported, shipped product intent (#3789) — not a misconfiguration. See Mode D, sec-9.
Explicitly out drawn only in Future design (Part III), never a build target
  • F1 event picker (Mode E) · F3 waitlist capture promise · F4 Stripe auto-settle (#3152, post-beta CPO ruling, shared engine item).
  • Not this assistant at all: deposit + balance is Retreat Booking (TMPL-RTBK01); own_form / a founder's own booking form + roster sheet is Workshop Booking's v1 scope, not Event's; one event per activation — two events = two assistants; no write-back to founder sheets, no Airtable/Notion.
Build status Beta — seeded (public) via migration 20260813130000
  • Shipped, per the PRD's own status log: the card row + config defaults · booking-receipt vs payment-receipt split, incl. the free-RSVP session-message confirm (#3789) · flow-intro date-token resolution (resolveFlowIntroText) · multi-event-on-one-number disambiguation (resolveBookingPhase0 / pickBookingMatchScored) · QR option disabled with a coming-soon badge (#4159 / PR #4201).
  • Drawn ahead of code, generalized from the shared booking engine: sec-6b rejected-template path · Nudge/attention actions (sec-8) · Attendees records surface (sec-13) — none of these are independently confirmed shipped for this card; verify against the live product before treating any of them as done.
  • Catalog copy is FINAL here — matches the PRD's ## Catalog Copy section verbatim (T1144 drift guard keeps the two in lockstep via check-catalog-copy.sh).
┌ CONFIG (Personalize) ────────────┐  ┌ BRAIN ──────────────┐  ┌ CATALOG COPY (DB row) ────────┐
│ event name · date* · price/     │  │ voice · policies ·  │  │ description · tldr ·          │
│ ticket · currency · spots ·     │  │ contact sign-off    │  │ benefits[4] · details.flow    │
│ payment_mode · link/keywords ·  │  └──────────┬──────────┘  │ (5 nodes) — sec-1/sec-2 only  │
│ copy fields · payment vehicle   │             │             └───────────────────────────────┘
└────────────────┬─────────────────┘             │ grounds answers
         reads   ▼                               ▼
        guest WhatsApp thread — capture (Flow form fronting typed capture, in_chat only)
                 │  name · contact · spots
                 ▼
        cohort_bookings row
                 │  booked → paid → confirmed (or, free_rsvp: booked → confirmed, no paid leg)
                 ├─► locked templates: confirmation (on Mark paid) + reminder (1d + 1h)
                 └─► Pulse metrics · attention rows · Attendees columns · activity feed
The three data shapes
  • Config schema (drawn in sec-3/3b, categories 01–04b — no 03b, no 05): event facts (event name, event date required — gates activation, same shared-engine gate as Workshop's AC-WSBK-16 ↗) · payment_mode (full_prepay/free_rsvp only) · generated wa.me link + conditional keywords · founder-editable copy (booking invite, payment ask).
  • Locked message copy: confirmation (event_booking_confirmation — own Meta name) + Night reminder (class_reminder — name frozen, body shared byte-for-byte with the rest of the booking family) submitted at activation via event-templates.ts submitEventTemplateSet, Meta-approved, never editable (C1). Every sec-9 ledger tags each message's source with one vocabulary: template founder field ai-composed native.
  • Booking record (cohort_bookings): name · contact · spots · status · paid-source (Mark paid only — no roster tick, v1 has no own_form). Read + verdict, never edit; the read-back enum is closed in code (_shared/flow-patterns.ts), and the Attendees columns come from the same per-assistant records contract (sec-13).

Founder vocabulary (Discover → Evaluate → Activate → Monitor) and guest vocabulary (Trigger → Interact → Outcome → Fallback) are the same pair every MystFlo assistant's Scope Council uses — reproduced here, not invented for this file. Unlike Workshop Booking, v1 has no booking_surface fork — every guest is captured in_chat; the real branch worth drawing here is at Payment, where payment_mode splits full-prepay from free RSVP.

┌ DISCOVER ────────────────────────────────────────────────────────┐
│ Founder browses Explore — sees the card, its price, and the      │
│ one required app (WhatsApp Business).                            │
└──────────────────────────┬─────────────────────────────────────────┘
                           ▼
┌ EVALUATE ──────────────────────────────────────────────────────────┐
│ Opens the Overview tab — what it does, how it works, key benefits. │
└──────────────────────────┬─────────────────────────────────────────┘
                           ▼
┌ ACTIVATE ──────────────────────────────────────────────────────────────┐
│ Personalize (event date/price per ticket/currency/spots · payment      │
│ mode · locked message copy) → Connect (WA number · template-approval   │
│ notice — no conditional Sheets pick, v1 has no own_form) → Try         │
└──────────────────────────┬─────────────────────────────────────────────┘
                           ▼
┌ MONITOR — founder, ongoing ─────────────────────────────────────────────┐
│ Activating (loader) → Activated (live, honest about pending templates) │
│ → Pulse: bookings roll in, attention rows, live feed                   │
└──────────────────────────┬─────────────────────────────────────────────┘
                           ▼   once live, per guest DM
                           ▼   the guest side runs across every scenario below
The guest side — every scenario across one spine

One waterfall could only draw one path. This reads left-to-right along the same six beats every guest passes through, with one lane per scenario — so the differences between modes, and the places where a lane simply stops, are visible in one look. Amber lanes are not built. Full detail per lane: sec-9.

Scenario
1 · Trigger
2 · Interact
3 · Payment
4 · Confirm
5 · Remind
6 · If they go quiet
A · Full payment, in chatfull_prepay · in_chat
the default
Taps the link, or just DMs. The AI reads intent — no keyword needed.
Assistant sends the native Flow form — invite names the program + date. Not approved yet → typed capture instead.
Thank-you first (“spot is held”), then the founder's own link as a Pay now button. Never inside the form.
Founder taps Mark paid → template lands <30s.
1 day + 1 hour before. One template, both times.
✗ Nothing. Spot held forever, founder never told.
B · Free RSVP, in chatfree_rsvp · in_chat
same as A
same as A — form drops the price/date field
— no payment leg at all
Automatic on submit — in-chat “you're in” note. The locked template stays for Mark paid.
same as A
Nothing to chase — the only clean lane.
Sold out → waitlistany mode · spots re-checked
at submit, not at send
same as A
Form submitted, but the last spot went. Guest is added to the waitlist and told so.
— no charge
✗ No confirmation — there's no spot
✗ No reminders
✗ Promise broken — "you'll be first to know" is never sent by anything.
Asks something elseany mode · already built
zero founder setup
mid-thread, any time
Answers from event details + Brain. Can't → On-Brand Support tries once, fact-checked.
unchanged
unchanged
unchanged
Still fails → founder takes over in the same thread. See sec-12.
D · Two live cards, one numberany payment · 2+ booking cards
shipped — see Mode D
“what's on?” or a bare “book” when 2+ booking cards share the number. Supported product usage, not a misconfiguration.
Deterministic name + date list — never the wrong Flow, never Brain improvisation. A unique full program name goes straight to that card's form.
then exactly A or B
then exactly A or B
then exactly A or B
inherits the picked lane
E · Picks from a listNOT BUILT
see sec-10
Just DMs — no link, no keyword. Intent reading already works.
Offers the open events; guest taps one. This is the missing piece.
then exactly A or B
then exactly A or B
then exactly A or B
inherits whatever A/B does

Column 6 is the honest column. Two of six lanes end in raw ✗ — see sec-11. Founder-side fallback (a template Meta rejects) is unchanged and lives at sec-6b; bookings and payment relay keep working throughout that.

Sequencing principle BINDING
  • Personalize's event date gates Try and activation — no real date, no activation (same shared-engine gate as Workshop's AC-WSBK-16 ↗, closing the silent-failure gap where reminders never scheduled and no one was told).
  • v1 has no booking_surface radio at all — Connect never shows a conditional roster-sheet pick, and there is no booking_form_url field. That gating logic (Workshop's AC-WSBK-15 ↗) does not apply to this card.
How the booking form is sent
  • Both entry points are one trigger path, not two. A wa.me link can't open a Flow form directly — it only pre-fills the guest's “book” message. The form is always sent BY the assistant, as an interactive flow message, once intent is caught. Link-tap and direct-DM converge on the identical mechanism.
  • The Flow fronts the typed capture — it doesn't replace it. The native form sends when the founder's Flow is published; otherwise the assistant falls back to typed capture, unchanged. An Event Booking instance behaves as typed-capture until its Flow is Meta-approved & published.
  • Payment is never inside the form. WhatsApp in-Flow payment is India/Singapore only — not the US. After the form submits, the assistant relays the founder's own external payment link, exactly like the typed path.
  • The form is auto-built per founder — no Meta Business Suite step. It's created, uploaded, and published on the founder's own WABA at activation. One published Flow per founder, version-swapped on config edits. Founders never open Flow Builder.
Part I — The flow every journey state, desktop then mobile, grouped by the PRD's phases — the 1:1 build contract, matched pixel for pixel. Parts live at full size in Part II · Components; deferred material in Part III · Future design, never a build target. Acceptance ids (AC-* ↗) live in prd-as-event-booking.md §Test Plan.
Discover
Discover “is this worth a tap?” · sec-1 the Explore card
Event Booking
⤴♡
Takes the booking, relays your payment link, and reminds them before it starts — so spots don't go empty and DMs don't pile up.
$1.75 per bookingBeta WhatsApp Business
Runs with On-Brand Support · each charged only when used
Personalize Connect Try
tap for detailsActivate
Frame 1 · Explore card [Discover]
  • Story — As a founder scrolling Explore, I see the real price and the one required app before I ever tap in, so there's no surprise once I start.
Design notes
  • Price is $1.75 per confirmed booking, flat fee. WhatsApp Business is the only required app — v1 has no own_form, so there is no conditional Google Sheet requirement on this card at all.
  • Pairing line (#5311, Peyton 2026-09-25 — provisional copy): same line as every booking card, from the booking-family rule in code — not a DB copy field, no price in it. Activation states are drawn once in workshop-booking.html → 3s / 6s; Pulse in assistant-lifecycle.html → 8f.
768 not yet drawn for this flow. Desktop and 375 are the contract.
This step is 1180. No 375 drawing — use the Desktop size.
Evaluate
Evaluate “what does it actually do?” · sec-2 the Overview tab
Event Booking
Takes the booking, relays your payment link, and reminds them before it starts — so spots don't go empty and DMs don't pile up.
✕
$1.75 per booking
Runs with On-Brand Support — Support answers your guests; booking takes the booking. Both run together, and each is charged per run.
Overview Personalize Connect Try
What it does

Event Booking captures the spot straight from WhatsApp — name, which event, how many tickets — then relays your ticket link exactly as you gave it. Free RSVPs confirm on the spot; paid spots wait for your “mark paid” tap. Either way, the guest gets a confirmation and a reminder the day before and an hour before it starts. Your ticket link stays yours; MystFlo never touches the money.

How it works
01
You
Teach it once

ticket link, policy, voice — from your Brain

02
Customer
Asks for a spot

picks an event, gives headcount

03
Assistant ✦
Captures + relays
✦AItakes the request, sends your ticket link
free RSVP mode -> skips the payment ask, straight to confirms + reminds
04
You
Marks it paid

one tap, or free RSVP confirms on capture

05
Assistant

Confirms + reminds

instant confirm, then 1 day + 1 hour before

Key benefits
Books the spot while you set up the room
Support answers the questions; the moment they say yes, spot logged and your link sent.
The people who RSVPd actually walk in
A reminder the day before and an hour before it starts — in your voice — so the room matches the headcount, not just the signup list.
Your ticket link, not a new one
Relays the link you already use — no new checkout to configure, no new platform to ask guests to join.
You mark paid — nothing confirms on a guess
Paid spots stay pending until you say so. Free RSVPs confirm on capture. Nothing changes to a confirmed status without your tap.
Frame 2 · Overview tab [Evaluate]
  • Story — As a founder, I read exactly what it does and how it works before I configure anything, so Personalize never asks me to decide something I don't understand yet.
Design notes
  • “Your ticket link, not a new one” is the promise this Overview tab makes — MystFlo relays the host's own vehicle, never takes the money (PRD Engine table). #5311 (provisional, pending Peyton): benefit 1 is Chief Product’s 2026-09-25 rewrite — the old card promised this assistant answers the inbox, which Support now owns; the header pairing line is UI, not a DB field. Until approved, the PRD’s Catalog Copy still holds the old card (next bullet).
  • Catalog copy matches the PRD verbatim — description, long_description, details.tldr, and all 4 benefit cards are copied from prd-as-event-booking.md §Catalog Copy, seeded by migration 20260813130000_evbk01_event_booking_card.sql and kept in lockstep by check-catalog-copy.sh (T1144).
  • "How it works" = production HowItWorksFlow idiom, nodes/subs/branch VERBATIM from the seeded details.flow (20260813130000_evbk01_event_booking_card.sql, 5 nodes · 1 branch); skeleton + ✦ only on the seed's one ai:true node; outcome tile = the single lit object. Panel at 720 for the ≥768px horizontal row; <768px renders the vertical rail (not drawn).
768 not yet drawn for this flow. Desktop and 375 are the contract.
This step is 1180. No 375 drawing — use the Desktop size.
Activate
Activate “set it up once” · sec-3/3b Personalize · sec-4 Connect · sec-5 Try
dimmed Explore page peeks — revealGap · app header stays visible above
Event Booking
Takes the booking, relays your payment link, and reminds them before it starts — so spots don't go empty and DMs don't pile up.
$1.75 per booking
✕
Overview› Personalize› Connect › Try
01 · Your event
Pick from what you sell quick-fill · #4824 · not built

Superseded by the master picker (Peyton 2026-09-28: one master design + per-assistant variations): one dropdown labelled “Which event is this for?”, placeholder “Pick an event”, fields on pick, plus the loading and couldn’t-load states — drawn once in workshop sec-3. The radio list below is the pre-dropdown frame.

Beach Sunset Mixer
$42 · 10 spots
✓
Rooftop Wine Tasting
$65 · 30 spots
Founders Breakfast
price on ask
+
Something new
Type it below. It stays with this event unless you add it on the last step.

Picking fills Name, Price, Where and Spots below — and Date and Time when your Brain knows them. Every one stays editable. Edit what you sell in your Brain →

Name * required
Beach Sunset Mixer
Price
42
Currency
USD · EUR/GBP/IDR/AUD/SGD/CAD/NZD
Date * required
2026-07-28
Time * required
18:00–21:00

A start, or a range — 18:00 or 18:00–21:00. An end at or before the start is flagged under the field, never blocks Continue.

Timezone
America/Toronto

So reminders fire at the right moment for a nomad host — never changes the time guests see.

Where * required always shown · #5249 · not built
The Offline Club, 88 King St W
https://maps.app.goo.gl/9kLp3XyQz2M… · map link, optional

Where guests show up — fills the confirmation, the reminders, and the invite. Starts as your business address and Maps link from your Brain; change either here if this event is somewhere else. With a map link guests get a one-tap “Open in Maps” button; without it they still see the address.

Spots
10

Edits here change only this event — your Brain stays as it is.

Field names match your Brain’s offering fields one-to-one — Name · Price · Date · Time · Where · Spots (Peyton 2026-09-23). Date and Time fill from the pick only when the offering carries them; Timezone is always yours.

Other states of 01 — same picker, same fields below it; only what your Brain holds and what you picked changes. Date · Time fill only when the offering carries them; Timezone is always typed by you.
Nothing in your Brain yet · the picker is an invite
Type it below — or add what you sell to your Brain →
+
Something new
Name · Price · Date · Time · Spots start empty. Where still starts as your business address.
One offering · one row + Something new
Beach Sunset Mixer
$42 · 10 spots
✓
+
Something new
Picked, then edited · price changed for this date
Beach Sunset Mixer
$42 · 10 spots
✓
Price
35
Currency
USD
Edits here change only this event — your Brain stays as it is.
The row keeps saying $42 — that is the Brain. This date runs at $35. No badge, no undo link: the quiet line is the whole explanation.
+ Something new picked · you type the offering
+
Something new
✓
Name *
e.g. Harbour Night Market
Price
25
Currency
USD
Spots
40
Where still starts as your business address. On the last step (Try) one row asks whether to add it to what you sell — off unless you switch it on (#4824, sec-5).
02 · How guests pay
Payment structure
Full prepay
Default. Guest pays in full to hold the spot.
Free RSVP
No payment — just a headcount.

Retreat-style deposits live on Retreat Booking, not here.

Where they pay spec change · #4703 · not built

Tick one or both. Whatever you tick goes out with your payment ask.

Payment link *
Any page guests can pay on — your ticketing page, Stripe, PayPal.
https://buy.stripe.com/8wM4kb…
QR code *
Upload your bank or PayNow QR once — sent as an image, guests scan and pay directly.
paynow-qr.png
Saved — sent with every payment ask
Replace Remove

Both are sent together — guests scan or tap, whichever suits them. With both ticked, the link and the QR are both required before you can activate.

Other states of this control — same two rows, only what’s ticked changes. Each input shows only while its vehicle is ticked (#4701).
One ticked · link only — today’s default
Payment link *
https://buy.stripe.com/8wM4kb…
QR code
Unticked → its input folds away. A QR you saved earlier is kept, just not sent.
Both ticked · QR still missing → blocks activation
QR code *
Drop your QR here — PNG or JPG

Add your QR to activate — or untick it.

Nothing ticked in a paid mode → both rows stay open with one line under them: Pick at least one way to get paid. Free RSVP → this whole group folds away — nothing to send.
Canvas fallback · file still saved · #4913
QR code *
paynow-qr.png
Saved — sent with every payment ask
ReplaceRemove

We couldn't convert this image — if the QR doesn't reach guests, re-upload from a different browser.

Upload succeeded; this is a warn, not a block; founder can Replace from another browser.

03a · How guests reach you — two ways in
1
Your booking link — share it anywhere
Bio, launch post, story, DM — one tap opens the chat.
Your booking link
Download QR
State — WhatsApp not connected
Your booking link
Connect WhatsApp to generate your booking link and QR.

The card never hides — it lights back up the moment Connect finishes.

2
They just text you — no link needed
Guests who already have your number, or reply to a story.

The AI reads intent — “any spots left tuesday?” books a spot just like “book” does. Nothing to configure.

Tell assistants apart conditional
book · count me in

Only if more than one assistant shares this number — these tell your assistants apart. Nothing to do with reading intent; most founders never see this field.

However they arrive — link tap or a direct message — it's one path in. v1 is in_chat only — no “where guests book” radio: WhatsApp's own tap-through form always fronts typed capture. Your booking list lives in Attendees — on this assistant, in Pulse. (Workshop Booking's own-form + roster-sheet radio is that card's v1 scope, not this one's.)

04a · Messages — in the order your guest gets them
Photo — optional · shown above the invite in the same bubble
+
No photo yet — drop one in, 1125×600 works best
1Booking invite — one field, same for every entry path · sent as a regular message, no approval needed
«event or event name» · «date» · «time» · «location» Tap below to grab your spot:

Shown above the invite button. Fills in your event name, date, time, and location automatically. No date set yet → the name-only line, never a blank.

2Booking form fixed — WhatsApp's own tap-through form (03b “In this chat”) · nothing to write
Name · Email · Party size · Date — filled from what you set in 01. “Your own booking form” (03b) sends your form link here instead.
3Payment ask — editable · sent as a regular message, no approval needed
Scan the QR to pay, or use the link below.

Sent right after the booking form, with your QR and/or link. The default follows what you ticked in 02 — QR only: “Scan the QR to pay.” · link only: “Use the link below to pay.” — and your edits stay. Lives here with the invite because it is your copy, not a setting.

4Booking confirmation locked
Meta-approved · not editable
Header: "You're booked — Beach Sunset Mixer"
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
5Night reminder locked
Meta-approved · not editable one preview, not two
Hi Maya, it's Priya — just a quick reminder, your spot's all set. If anything comes up, reply here and let me know. See you soon!
04b · Reminder channels — required
WhatsAppEmail

Which channels the night reminder can use — connect the app for each in Connect. WhatsApp on by default; the booking flow itself always runs in chat regardless.

No “05 · When I need to step in” category on this card — that's Workshop Booking's own aspirational, not-built PRD debt (its AC-WSBK-36/37/38), not confirmed for Event Booking. Handover to the founder still works in-thread — see sec-12.

Where you get pinged
WhatsApp — this thread
Always on. Reply from your phone as usual.
Connected ⌄
Slack
A copy in your channel. On the app? You reply in WhatsApp. API-only number? Reply right in the thread — it reaches your guest.
Connect
·· preview — “In this chat” · once your Flow is live ··
hi! any spots left for the mixer?
1 · Booking invite
Beach Sunset Mixer
· Jul 28, 2026
· 18:00–21:00
· The Offline Club, 88 King St W
Tap below to grab your spot:
When a photo is set (Photo field, above), this SAME bubble sends with the photo as its image header:
photo header · 1.91:1
Beach Sunset Mixer
· Jul 28, 2026
· 18:00–21:00
· The Offline Club, 88 King St W
Tap below to grab your spot:
2 · Booking form — fixed
Beach Sunset Mixer
Tue 28 Jul · $42 per ticket · 3 spots left
Name
Email
Party size
2026-07-28
Confirm booking
Real form fields after activation — never faked here
3 · Payment ask — link only shown; QR / both variants below
Your details are in, Maya. Your spot for Beach Sunset Mixer is held — it's confirmed the moment your payment lands.

Use the link below to pay.
↗ Pay now
This rail shows the variant matching what’s ticked in 02 · How guests pay. Link only = the bubble above (URL lifted onto the Pay now button). QR only — this bubble sends instead:
your QR as an image — caption: the thank-you + “Scan the QR to pay.”
Both ticked (#4703) — ONE bubble, no button (an image can’t carry one): your QR as the image, the caption = thank-you + payment ask + your link on its own line, tappable:
Your details are in, Maya. Your spot for Beach Sunset Mixer is held — it's confirmed the moment your payment lands.

Scan the QR to pay, or use the link below.
https://buy.stripe.com/8wM4kb…
done — just sent it!
you tap Mark paid → 4 · Booking confirmation
You're booked — Beach Sunset Mixer
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
5 · Night reminder — same thread, 1 day + 1 hour before it starts

Works the same whether they tapped your wa.me link or typed this directly — the AI reads intent, not just the word. No second, dimmed “own booking form” preview here — v1 has only this one path.

Frame 3b · Personalize (desktop split) [Activate]
  • Story — As a founder on desktop, I fill the same Personalize fields while watching a live preview of how the assistant will actually reply — same fixes, wider canvas.
Acceptance
  • Same criteria as sec-3 — this frame draws the desktop width of the identical fix, not a second behavior.
Design notes
  • See sec-3 for the full rationale on every fix shown here (required date, dead-toggle removal, radio bug, entry-link parity, the conditional “Tell assistants apart” block, the cancellation-policy removal, the 04a/04b Messages split — no 03b, no 05, both dropped since v1 has no own_form and no step-in category). This frame exists only to show the same content at the desktop split-pane width, alongside the live preview column.
  • #4913 fallback note: see sec-3 — drawn in the Other-states call-out; happy path stays quiet; no-frame gap closed.
01 · Pick from what you sell — quick-fill, then personalise [Activate · Peyton 2026-09-23 · #4824]
  • Story — As a founder, I pick the event I already taught my Brain and the form fills itself — then I change what’s different about this date without touching my Brain.
Acceptance
  • The cohort_name + price the pick fills are exactly what the Flow form’s title and date · price line render (shared engine — Workshop’s AC-WSBK-22 ↗).
  • AC-COHB-28 ↗ · AC-COHB-31 ↗ (booking family, names TMPL-EVBK01) — Personalize location + map_url are answerable mid-chat and outrank the Brain’s business location, so both always render here — never hidden because the Brain has an address.
  • NEW (PRD debt — #4824) — a vertical radio list of brain_profile.services[] (name · price · capacity) tops 01; a pick fills Name, Price + Currency, Where (and Date · Time when the offering carries them), Spots — all stay visible and editable, grouped Name · Price · Date · Time · Where · Spots (Peyton 2026-09-23 — the shared Brain offering vocabulary, one-to-one; supersedes the #4478 field order); the picker is the shipped OfferingPicker.tsx (T1092) widened from RTBK01 to this card, minus COHORT_PICKER_FIELD_KEYS filtering (ConfigureStep.tsx:267).
  • NEW (PRD debt — #5249) — Where (address, map link beneath it as a second field) always renders, prefilled from the Brain (contact_info.address · maps_url) when known; today Location hides when the Brain has an address. Start time + End time collapse to ONE Time field (“18:00” or “18:00–21:00”) with Timezone beside it.
  • NEW (PRD debt — #4824) — a pick writes wizard configuration only; the Brain is never written from Personalize. Date · start/end time · timezone are never quick-filled.
Design notes
  • Row grammar = OfferingPicker.tsx (T1092) verbatim: radio rows ≥44px, name text-sm white/80 + price sub-line white/40 (“price on ask” when empty), ✓ on the picked row, “Edit what you sell in your Brain →” microline; vertical-first, no modal (kit 2026-06-12 lock). Only the last row is new: dashed “+ Something new” replaces the inline name/price composer — typing happens in the real fields below, not a nested mini-form.
  • One quiet line, once: “Edits here change only this event — your Brain stays as it is.” sits under Spots — the last of the six quick-filled fields, now contiguous (Name · Price · Date · Time · Where · Spots); Where keeps its own helper naming the Brain source, and a 9.5px review line under the quiet line states the one-to-one match with the Brain’s offering fields. No per-field “from your Brain” ticks (kit 2026-06-30 cheap-tick lock).
  • Persona words only in copy, shared words on labels: the field labels are the Brain’s offering vocabulary on every card (Name · Price · Date · Time · Where · Spots — Peyton 2026-09-23); ticket · spots (never seat) live in helpers, offering rows and previews — the engine home workshop-booking.html sec-3b is the same contract in workshop words; grade Event pixels against THIS file.
  • Six states, one control: several offerings (main frame) · nothing in the Brain (invite: “Type it below — or add what you sell to your Brain”) · one offering · picked-then-edited · + Something new — all in the dashed call-out; the Try-step ask is sec-5 / 5m. 768: single column at every width, no separate frame (canon U8).
dimmed Explore page peeks — revealGap · app header stays visible above
Event Booking
Takes the booking, relays your payment link, and reminds them before it starts — so spots don't go empty and DMs don't pile up.
$1.75 per booking
✕
Overview› Personalize › Connect› Try
01 · Connect apps
Communication WhatsApp Business ▴
Matches your Reminder channels from Personalize — WhatsApp always required for booking chat
Connected ⌄
WhatsApp
◉WhatsApp Business
Which number should this assistant answer on?
+1 555-0142 ▾
Email — if picked in Personalize
○Resend
Not required — Email wasn't picked as a reminder channel
Not connected
AI Model MystFlo AI
Connected by default. Choosing your own AI model is coming later.
Connected ⌄
Storage MystFlo Storage
Your Attendees list on Pulse — every booking lands there, zero clicks. Nothing external to connect — v1 has no own-sheet option.
Connected
Two message templates are waiting on approval Your booking confirmation and night reminder need one-time approval from Meta before they can send — usually resolved within a day or two of activation. Bookings still capture and your payment link still relays in the meantime.
02 · Wallet
Wallet balanceEach confirmed booking draws $1.75 from your prepaid balance $86.40
03 · Brain Check
Your business
✓Business name
Confirmed
Edit in Brain →
Where to find you
✓Contact
For sign-off · Confirmed
Edit in Brain →
Branding
✓Brand voice
Confirmed
Edit in Brain →
How you run
✓Cancellation policy
Confirmed
Edit in Brain →
4 of 4 ready — it can answer guests
Frame 4 · Connect tab [Activate]
  • Story — As a founder, I connect the WhatsApp number this assistant should answer on, see that my Attendees home is already connected with nothing to set up, and see plainly that two templates are waiting on Meta before I activate.
Acceptance
  • AC-BILL-04 ↗ — activation is blocked unless both a stripe_meter_id and a stripe_price_id exist on this template's row; the Wallet row above surfaces the same wallet that guard protects (largely legacy under prepaid — price_per_run is the debitable amount, per the PRD's Billing section).
  • No own_form-conditional acceptance criteria on this card — v1 has no roster-sheet gate, no booking_form_url, no conditional Storage withholding. Workshop's AC-WSBK-15 ↗ does not apply here.
Design notes
  • A tethered WhatsApp-number picker is required when the connected account has more than one number, via the right-aligned +1 555-0142 ▾ control on the WhatsApp pick-row. Every row carries its ROLE label (ROLE_IDS, required-roles.ts): “AI Model” → MystFlo AI · Connected, “Storage” → MystFlo Storage · Connected — this card has exactly one Storage state (always in-chat), so the row is drawn plainly connected, no caret, no withheld/conditional variant to document.
  • Communication carries a caret (shared engine convention, kit design-decisions.md 2026-08-05/07) — Personalize's “04b · Reminder channels” field is a DECISION only (WhatsApp always required, Email optional), and the caret expands into one pick-group per channel. Drawn NOT-CONNECTED for Email here deliberately — Event's booking confirmation and Night reminder are locked, Meta-approved WhatsApp templates (04a) with no Resend-side equivalent copy yet; picking “Email” without that copy existing is a real build gap, flagged not solved in this pass.
  • One connect list, no badge tier (Peyton 2026-08-04, shared engine convention): MystFlo AI and MystFlo Storage are real rows in 01 · Connect apps, not an “Included” group. AI is Connected by default (a future model choice is named, future-tense only — never “bring your own key” today). MystFlo Storage is Connected with zero clicks — always, on this card, since there is no own-sheet alternative.
  • Template-approval banner: plain, factual, no internal names — repeated at success (sec-6). Brain check reads four sections: business name + brand voice, cancellation policy, founder contact info.
Overview Personalize Connect Try
What you're activating
EventHarbour Night Market · new
Date2026-07-28
Price · Spots$25 · 40 spots
PaymentFull prepay · in this chat
Messagesconfirmation + reminder — locked, pending Meta
Brain✓ 4 of 4 ready
Wallet$86.40 · $1.75 per booking
Trying it
Replies are triggered by real customer messages Message your WhatsApp after activation to see it live. There's no way to fake a guest message before then — so we don't pretend to.
Send yourself the first one Right after you activate, message your own business number and go through the booking flow yourself — pick a spot, see the confirmation, the real thing.
Name this one
Harbour night market
Add it to what you sell? #4824 · not built
Add “Harbour Night Market” to what you sell?
Your other assistants will quote it. Edit anytime in Brain.

Off unless you switch it on. Shows only when 01 used “+ Something new” — hidden when the name is already in your Brain.

Other states of this row.
Switched on
Add “Harbour Night Market” to what you sell?
Added when you activate — $25 · 40 spots lands in What you sell.
The Brain write happens at activation, in the same pipeline step as the event seed — never at pick time, never on Continue.
Hidden · you picked an existing offering, or typed a name already in your Brain
No row, not even greyed — there is nothing to decide. “Name this one” sits directly above Activate, as before.
Activate — $1.75 per booking

Runs on your WhatsApp from the moment you activate. Pause anytime.

Frame 5 · Try tab [Activate]
  • #5311 (2026-09-25): this read-back predates the Support pairing. The target is drawn once in workshop-booking.html → 3s and applies here unchanged except for the card name: a one-line heads-up on Personalize, On-Brand Support as a selected, locked add-on in Connect (Active if already live), a read-back of both assistants, and “Activate both”.
  • Story — As a founder, I see exactly what I'm about to turn on — date, price, payment/booking mode, message status, Brain readiness, wallet — in one read-back before I commit.
Acceptance
  • AC-WSBK-16 ↗ — the required date shown in the read-back is what Try/activation actually gates on.
  • AC-BILL-04 ↗ — the wallet balance shown here is what the same-name guard checks before activation succeeds.
Design notes
  • Honest test — same pattern as every WhatsApp assistant: no pre-activation test run exists for chat-triggered assistants; the master contract (assistant-lifecycle.html frame 6) covers this once for every assistant type instead of restating it here. The read-back's "Messages" row already flags the pending Meta approval, so activating isn't a surprise when the success screen repeats it (sec-6).
  • #3138 — self-test nudge is booking-specific: "Send yourself the first one" tells the founder to go through the actual booking flow (pick a spot, see the confirmation) rather than the master contract's generic "message your own number" line — a single reply doesn't prove booking capture works, walking the flow does. First-time activation must never show a real "Run test" button or an "activate without a test run?" confirm dialog for this template — both notices above are the whole Try experience, on every activation path.
“Add it to what you sell?” — the one explicit ask [Activate · Peyton 2026-09-14 / 2026-09-23 · #4824]
  • Story — As a founder who typed an event my Brain didn’t know, I decide on the last step whether it joins what I sell — never silently, never pre-ticked.
Acceptance
  • NEW (PRD debt — #4824) — when 01 used “+ Something new”, one row renders directly above Activate: an OFF-by-default toggle labelled “Add ‘[name]’ to what you sell? Your other assistants will quote it. Edit anytime in Brain.”
  • NEW (PRD debt — #4824) — the row is hidden (not greyed) when the typed name already exists in brain_profile.services[] or an existing offering was picked.
  • NEW (PRD debt — #4824) — ON → the offering (name · price · currency · capacity) is appended to services[] once, at activation; OFF → the Brain is untouched. Re-activation never re-asks for a name that now exists.
Design notes
  • This frame now draws the “+ Something new” path (read-back gains an Event row, $25 · 40). The picked-existing path is this same screen minus the ask row — Name this one sits directly above Activate.
  • A toggle, not a checkbox row: a switch reads as a decision with a default; the OFF default is Peyton’s 2026-09-14 ruling (explicit ask, never pre-ticked). Copy is the ruled string verbatim — no “recommended” nudge.
  • Hidden, not greyed, when not applicable — deliberate exception to the kit 2026-08-10 “grey and explain” lock, on Peyton’s 2026-09-23 instruction: when nothing is being added there is no impossible action to explain.
  • 375: 5m. 768: same single column.
768 not yet drawn for this flow. Desktop and 375 are the contract.
Closed — the form keeps the full working height
dimmed Explore peeks — tap to return
Overview Personalize Connect Try
01 · Your event
Pick from what you sell quick-fill · #4824 · not built

Superseded by the master picker (Peyton 2026-09-28: one master design + per-assistant variations): one dropdown labelled “Which event is this for?”, placeholder “Pick an event”, fields on pick, plus the loading and couldn’t-load states — drawn once in workshop sec-3. The radio list below is the pre-dropdown frame.

Beach Sunset Mixer
$42 · 10 spots
✓
Rooftop Wine Tasting
$65 · 30 spots
Founders Breakfast
price on ask
+
Something new
Type it below.

Picking fills Name, Price, Where and Spots below — and Date and Time when your Brain knows them. Every one stays editable. Edit what you sell in your Brain →

Name * required
Beach Sunset Mixer
Price
42
Currency
USD
Date * required
2026-07-28
Time * required
18:00–21:00

A start, or a range — 18:00 or 18:00–21:00.

Timezone
America/Toronto
Where * required always shown · #5249
The Offline Club, 88 King St W
https://maps.app.goo.gl/9kLp3XyQz2M… · map link, optional

Starts as your business address and Maps link from your Brain — change either if this event is somewhere else.

Spots
10

Edits here change only this event — your Brain stays as it is.

Field names match your Brain’s offering fields one-to-one — Name · Price · Date · Time · Where · Spots.

·· preview — “In this chat” · once your Flow is live ··
hi! any spots left for the mixer?
1 · Booking invite
Beach Sunset Mixer
· Jul 28, 2026
· 18:00–21:00
· The Offline Club, 88 King St W
Tap below to grab your spot:
When a photo is set, this SAME bubble sends with the photo as its image header (see desktop frame).
2 · Booking form — fixed
Beach Sunset Mixer
Tue 28 Jul · $42 per ticket · 3 spots left
Name
Email
Party size
2026-07-28
Confirm booking
Real form fields after activation — never faked here
3 · Payment ask — link only shown; QR / both variants below
Your details are in, Maya. Your spot for Beach Sunset Mixer is held — it's confirmed the moment your payment lands.

Use the link below to pay.
↗ Pay now
This rail shows the variant matching what’s ticked in 02 · How guests pay. Link only = the bubble above (URL lifted onto the Pay now button). QR only — this bubble sends instead:
your QR as an image — caption: the thank-you + “Scan the QR to pay.”
Both ticked (#4703) — ONE bubble, no button (an image can’t carry one): your QR as the image, the caption = thank-you + payment ask + your link on its own line, tappable:
Your details are in, Maya. Your spot for Beach Sunset Mixer is held — it's confirmed the moment your payment lands.

Scan the QR to pay, or use the link below.
https://buy.stripe.com/8wM4kb…
done — just sent it!
you tap Mark paid → 4 · Booking confirmation
You're booked — Beach Sunset Mixer
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
5 · Night reminder — same thread, 1 day + 1 hour before it starts

Works the same whether they tapped your wa.me link or typed this directly — the AI reads intent, not just the word. No second, dimmed “own booking form” preview — v1 has only this one path.

01 · other states — nothing in your Brain yet · + Something new
dimmed Explore peeks — tap to return
Overview Personalize Connect Try
01 · Your event
State — nothing in your Brain yet

Type it below — or add what you sell to your Brain →

+
Something new
Type it below.
✓
Name * required
e.g. Harbour Night Market
Price
25
Currency
USD
Date * required
Pick a date
Time * required
18:00 or 18:00–21:00
Where * required
The Offline Club, 88 King St W
Map link, optional

Still starts as your business address — the one thing the Brain can fill without an offering.

Spots
40
State — several offerings, + Something new picked
The three Brain rows stay listed above the dashed row; the dashed row is the one with the ✓. Fields below are empty exactly as here. On Try, one row asks whether to add it to what you sell — off by default (5m).
Scrolled — payment, booking surface, message copy
dimmed Explore peeks — tap to return
02 · How guests pay
Payment structure
Full prepay
Guest pays in full to hold the spot. Default.
Free RSVP
No payment — just a headcount.

Retreat-style deposits live on Retreat Booking, not here.

Where they pay spec change · #4703 · not built

Tick one or both. Whatever you tick goes out with your payment ask.

Payment link *
Any page guests can pay on — your ticketing page, Stripe, PayPal.
https://buy.stripe.com/8wM4kb…
QR code *
Upload your bank or PayNow QR once — sent as an image, guests scan and pay directly.
paynow-qr.png
Saved — sent with every payment ask
Replace Remove

Both are sent together — guests scan or tap, whichever suits them. With both ticked, the link and the QR are both required before you can activate.

Other states of this control — same two rows, only what’s ticked changes. Each input shows only while its vehicle is ticked (#4701).
One ticked · link only — today’s default
Payment link *
https://buy.stripe.com/8wM4kb…
QR code
Unticked → its input folds away. A QR you saved earlier is kept, just not sent.
Both ticked · QR still missing → blocks activation
QR code *
Drop your QR here — PNG or JPG

Add your QR to activate — or untick it.

Nothing ticked in a paid mode → both rows stay open with one line under them: Pick at least one way to get paid. Free RSVP → this whole group folds away — nothing to send.
Canvas fallback · file still saved · #4913
QR code *
paynow-qr.png
Saved — sent with every payment ask
ReplaceRemove

We couldn't convert this image — if the QR doesn't reach guests, re-upload from a different browser.

Upload succeeded; this is a warn, not a block; founder can Replace from another browser.

Scrolled further — how guests reach & book you, message copy, when to step in
dimmed Explore peeks — tap to return
03a · How guests reach you — two ways in
1
Your booking link — share it anywhere
Bio, launch post, story, DM — one tap opens the chat.
Your booking link
Download QR
State — WhatsApp not connected
Your booking link
Connect WhatsApp to generate your booking link and QR.

The card never hides — it lights back up the moment Connect finishes.

2
They just text you — no link needed
Guests who already have your number, or reply to a story.

The AI reads intent — “any spots left tuesday?” books a spot just like “book” does. Nothing to configure.

Tell assistants apart conditional
book · count me in

Only if more than one assistant shares this number — these tell your assistants apart. Nothing to do with reading intent; most founders never see this field.

However they arrive — link tap or a direct message — it's one path in. v1 is in_chat only — no “where guests book” radio. Your booking list lives in Attendees — on this assistant, in Pulse.

04a · Messages — in the order your guest gets them
Photo — optional · shown above the invite in the same bubble
+
No photo yet — drop one in, 1125×600 works best
1Booking invite — one field, same for every entry path · sent as a regular message, no approval needed
«event or event name» · «date» · «time» · «location» Tap below to grab your spot:

Shown above the invite button. Fills in your event name, date, time, and location automatically. No date set yet → the name-only line, never a blank.

2Booking form fixed — WhatsApp's own tap-through form (03b “In this chat”) · nothing to write
Name · Email · Party size · Date — filled from what you set in 01. “Your own booking form” (03b) sends your form link here instead.
3Payment ask — editable · sent as a regular message, no approval needed
Scan the QR to pay, or use the link below.

Sent right after the booking form, with your QR and/or link. The default follows what you ticked in 02 — QR only: “Scan the QR to pay.” · link only: “Use the link below to pay.” — and your edits stay. Lives here with the invite because it is your copy, not a setting.

4Booking confirmation locked
Meta-approved · not editable
Header: "You're booked — Beach Sunset Mixer"
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
5Night reminder locked
Meta-approved · not editable
Hi Maya, it's Priya — just a quick reminder, your spot's all set. If anything comes up, reply here and let me know. See you soon!

One preview, not two — the 1-day and 1-hour sends use the identical body.

No “05 · When I need to step in” category on this card — that's Workshop Booking's own aspirational, not-built PRD debt (its AC-WSBK-36/37/38), not confirmed for Event Booking.

Where you get pinged
WhatsApp — this thread
Always on. Reply from your phone as usual.
Connected ⌄
Slack
A copy in your channel. On the app? You reply in WhatsApp. API-only number? Reply right in the thread — it reaches your guest.
Connect
Frame 3 · Personalize (375) [Activate]
  • Story — As a founder, I set my event's date, price, spots, currency, how guests pay, and my message copy in one place — the assistant needs nothing else to run.
Acceptance
  • Required event date gates Try and activation (same shared-engine gate as Workshop's AC-WSBK-16 ↗).
  • An event configured free_rsvp skips the payment-ask leg entirely, straight to confirmed (shared engine — Workshop's AC-WSBK-05 ↗).
  • payment_mode renders as a real radio the founder can select — no booking_surface radio on this card (Workshop's AC-WSBK-17 ↗ only partly applies — the payment half, not the surface half).
  • A guest tapping the event's shareable wa.me link drops straight into the booking flow (shared engine — Workshop's AC-WSBK-01 ↗).
Design notes
  • Categories 01 · 02 · 03a · 04a/04b — no 03b, no 05: 03a “how guests reach you” = two equal entry cards (booking link · they-just-text, AI reads intent — nothing to configure). There is no “03b · where guests book” radio — v1 is in_chat only, Attendees on Pulse is the one destination. No “05 · when I need to step in” category — that's Workshop's own aspirational debt, not confirmed here.
  • Conditional grammar: “Tell assistants apart” keywords (routing disambiguation ONLY — never intent; renders only with 2+ assistants on the number) draw as a dimmed cond-block. Dead controls omitted outright: deposit_balance (a Retreat Booking concept, per the PRD's own Constraints section).
  • 04a is one flow, in guest order (Peyton, preview review 2026-09-10 addendum): 1 Booking invite (editable) → 2 Booking form (fixed — WhatsApp’s own) → 3 Payment ask (editable; default “Scan the QR to pay, or use the link below.” — QR only “Scan the QR to pay.”, link only “Use the link below to pay.”; helper “Sent right after the booking form, with your QR and/or link.”) → 4 Booking confirmation (locked) → 5 Night reminder (locked). Each numbered row carries the same number as the .beat above the bubble it produces in the phone rail, so a founder reads the form top-to-bottom and watches the chat happen in that order. The old “Here’s how to pay…” default is superseded in these frames only — sec-7/sec-9 still draw today’s shipped copy until the PRD reseeds it.
  • Messages: Booking invite + Payment ask = regular session messages, stated in their fine print (never submitted to Meta); the invite's default names the program + date before the Book-now button (“{{workshop_name}} — {{date}}. Tap below to grab your spot:” — {{program}} aliases {{workshop_name}} per the PRD's Guest receipts section, #3789); Booking confirmation + Night reminder = Meta-approved locked-tpl previews, copy verbatim (C1; ONE reminder body covers both offsets). Payment vehicles (#4703, spec change — not built) — folded into 02 · How guests pay under the pay-structure radio (Peyton, preview review 2026-09-10); Payment ask stays in 04a with the invite: a checkbox GROUP of two vehicles — Payment link (URL nested under it) · QR code (upload nested under it, #3153 shipped) — tick one or both; both ticked → both required, one bubble (QR image, caption carries the link). Mirrors workshop-booking.html sec-3/3b verbatim (shared engine); rationale + guard-rail states live there. The sec-8c locked mirror below still draws the shipped three-option radio — it follows once the PRD locks the field shape.
  • #4913 fallback note is drawn in the Other-states call-out; happy path stays quiet; no-frame gap closed.
  • Preview = the full conversation in ONE pv-chat (invite → Flow form → payment ask → Mark-paid beat → confirmation → reminder placeholder; scrolls in the ~460px shell, position:sticky in the build) — one preview only, no second dimmed own-form phone (that's Workshop's AC-WSBK-14 ↗, not this card's). Event date required, date-only, gates Try/activation.
01 · Pick from what you sell — 375 [Activate · Peyton 2026-09-23 · #4824]
  • Story — Same as the desktop note on sec-3b: pick the event the Brain already knows, the form fills itself, change only what differs for this date.
  • Acceptance — identical to sec-3b (AC-WSBK-22 ↗ shared engine · AC-COHB-28 ↗ · AC-COHB-31 ↗ · the three NEW (PRD debt) rows); this tier draws the same contract at 375, not a second behaviour.
  • 375 rules: radio rows are full-width ≥44px tap targets, the ✓ hangs right; Price + Currency share one row (the same sm 640 pair rule as the time pair) and stay side by side even at 375 because both values are short; the quiet line follows the Spots field; Time and Timezone stack at 375 (the shipped sm 640 pair rule) and the map link sits under Where as a second field. The two alternate states drawn as a second full phone (nothing in your Brain · + Something new) — the one-offering and picked-then-edited states differ only by row count / one field value, so they ride the desktop call-out.
Off — the default; nothing is added unless you say so
dimmed Explore peeks — tap to return
Overview Personalize Connect Try
What you're activating
EventHarbour Night Market · new
Date2026-07-28
Price · Spots$25 · 40 spots
Wallet$86.40 · $1.75 per booking

Payment · Messages · Brain rows and the two “Trying it” notices are unchanged from sec-5 — cropped here.

Name this one
Harbour night market
Add it to what you sell?
Add “Harbour Night Market” to what you sell?
Your other assistants will quote it. Edit anytime in Brain.
Activate — $1.75 per booking

Runs on your WhatsApp from the moment you activate. Pause anytime.

On — added when you activate
dimmed Explore peeks — tap to return
Overview Personalize Connect Try
Name this one
Harbour night market
Add it to what you sell?
Add “Harbour Night Market” to what you sell?
Added when you activate — $25 · 40 spots lands in What you sell.
Activate — $1.75 per booking

Runs on your WhatsApp from the moment you activate. Pause anytime.

State — hidden
Picked an existing offering, or typed a name already in your Brain → no row at all; Name this one sits directly above Activate.
Frame 5m · Try (375) — the ask row [Activate · #4824]
  • Story — Same as sec-5: I decide on the last step, on my phone, whether the event I typed joins what I sell.
  • Acceptance — identical to the sec-5 “Add it to what you sell?” block (three NEW (PRD debt — #4824) rows).
  • 375 rules: the row is ≥44px with the switch as the only control, label wraps to two lines, the sub-line changes with the state (ask copy off · “Added when you activate” on); the row sits directly above Activate with nothing between them. Off and On are both drawn; hidden is stated, not drawn — there is nothing to draw.
Live
Monitor “is it working?” · sec-6a Activating · sec-6 Activated · sec-8 Pulse
Activating "Weekly sunset mixer"

About 20 seconds. Stay on this screen.

✓Checking your setup
✓Confirming billing & terms
✓Setting up your assistant
◍Running a live test~15s
Going live
Frame 6a · Activating loader [Monitor]
  • Story — As a founder, I watch real setup steps complete in order — not a fake spinner — so waiting feels like something is actually happening.
Acceptance
  • Covered by the master lifecycle contract's own criteria (assistant-lifecycle.html / the activation-wizard PRD), not restated here — this card contributes no new criterion for the generic loader.
Design notes
  • Same C2 pipeline as every assistant: wording ported verbatim from the master loader (assistant-lifecycle.html frame 7a) — no assistant-specific rewording, same 5 grouped steps.
✓
Event Booking is live

It's capturing spots on your WhatsApp now. Watch bookings fill in Pulse.

✓ WhatsApp connected ✓ Brain confirmed — 4 of 4 sections
Confirmations & reminders — pending Meta approval Two message templates are still waiting on Meta's one-time approval — usually a day or two. Bookings capture and your payment link relays right now; confirmations and reminders will start sending once approval comes through. If either is rejected instead, you'll get a clear fix step here — never a silent stall (see sec-6b below).
View in Pulse Done
Frame 6 · Activated / success [Monitor]
  • Story — As a founder, the moment activation finishes I know it's live, what's already true (WhatsApp connected, Brain confirmed), and exactly what's still pending — never a surprise later.
Acceptance
  • AC-WSBK-18 ↗ — when templates are pending Meta approval at activation success, the screen states that honestly and never claims confirmations/reminders already send; bookings and payment relay keep working underneath.
Design notes
  • Checklist carries only always-true facts (WhatsApp connected, Brain confirmed); the pending-Meta wait is its own amber notice, never folded into the green list. Headline hedges; "usually a day or two" stays in sub-copy (matches sec-4's banner).
Active ▶⏸⏹ Details →
Weekly sunset mixer ASST-4L9
Event Booking
1 connected · reminders pending Meta approvalⓘ
Bookings
9 confirmed · 2 awaiting payment · $15.75 · 2h ago
Attendeeswho's coming, who still owes you7 booked · 3 left ›
Needs your attention
!
+1 ••• ••• 7742 — booked, not yet paid
Booked 44 min ago · payment ask sent, no reply yet
Nudge
!
2 templates pending Meta approval
Confirmations & reminders hold until approved
Details
Today
Booking confirmed — Priya M.+1 ••• ••• 2210 · paid $42 6:14 PM
Payment link relayed+1 ••• ••• 7742 · awaiting payment 5:30 PM
HeldNight reminder queued — pending template approvalWill send once Meta approves tonight 6 PM
Booking confirmed — Daniel R.+44 •• •• 9031 · paid $42 2:05 PM
Frame 8 · Pulse [Monitor]
  • Story — As a founder, I watch bookings and payments roll in and see exactly who still needs a nudge, without depending on push notifications that don't exist yet.
Acceptance
  • AC-WSBK-06 ↗ — a payment signal arriving after the session's start time is flagged for the founder to confirm/decline, never auto-confirmed — this is what an "attention" row like the unpaid booking represents.
  • AC-WSBK-07 ↗ — duplicate captures from the same phone don't create a second row, so the feed never double-counts one guest.
Design notes
  • Honest fallback while notifications aren't built: even though the push-notification toggles are hidden (sec-3 — no runtime reader exists), Pulse itself always shows unpaid/pending bookings the moment the founder opens it. Real detection, not the broken notify system standing in for it.
  • Card geometry, controls, and feed pattern reuse the master's fixed zone map (assistant-lifecycle.html) — same shell as every Pulse card, only the metrics line and feed content are Event-Booking-specific.
  • Attendees is a slot IN the card (2026-09-02, master frame 8d): last zone after Bookings — noun · "7 booked · 3 left ›" — collapses to zero on a non-records card, never renders for guests, taps into the detail sheet with the records sheet open (sec-13). Not built.
  • Meta line uses the platform's D-3 convention: amber lives on the meta line only, never the status badge — "Active" stays green because bookings and payment relay already work; only the outbound reminder send is waiting.

Two things sit behind “route to the founder”: how the handover physically works (settled — it happens in the same WhatsApp thread, no second app needed) and what the founder can decide about it (nothing today — and one of those decisions costs her money, so it can't stay silent).

MechanismIt happens in the same chat — Slack is only a mirror
shippedWhatsApp Coexistence · Meta, May 2025
   ONE number — the founder's own. Two things talk on it at once.

   ┌───────────────────────────────┐        ┌──────────────────────────────┐
   │  WhatsApp Business APP        │◄──────►│  Cloud API (MystFlo)         │
   │  on Priya's phone             │ mirror │  the assistants              │
   │  she types here, by hand      │ in     │  they answer here            │
   └───────────────┬───────────────┘ real   └───────────────┬──────────────┘
                   │                 time                   │
                   └────────────────────┬────────────────────┘
                                        ▼
                              ┌──────────────────┐
                              │  the guest's     │   one thread.
                              │  WhatsApp chat   │   one number.
                              └──────────────────┘   no handoff seam.

   escalation fires ──► assistant goes SILENT (human mode)
                   ├──► guest told "I'm connecting you with Priya"
                   ├──► Priya opens WhatsApp and replies herself
                   └──► Slack (optional): a copy lands in her channel
                        ── one-way. She CANNOT reply from Slack. ──
Design notes
  • Since May 2025, WhatsApp Coexistence lets the founder's own number run the Business App and the Cloud API at once, mirroring in real time — the founder keeps her phone and replies by hand in the same thread the assistant was just in. No second app is needed.
  • Slack is a one-way mirror today: escalations and guest messages post into the founder's channel, and she cannot answer from there.
  • A founder whose number is Cloud-API-only (migrated before coexistence existed, or onboarded API-only) has no Business App to reply from — for that class of founder, “route to the founder” currently has no way for her to actually answer.
  • The holding line — “I'm connecting you with Priya” — should degrade to what's true for founders who have no reply path yet, rather than promise a human who can't currently speak.
Gap — cross-referenced, not this card'sThe founder can't set any of this — not proposed as a category here
NOT BUILT · Workshop Booking's own 05 proposal (sec-3/sec-3b there), not confirmed for this card
Design notes
  • Same real gap on this card — OBS second-attempt escalation is a PAID AI run that fires automatically today with no opt-in, and the holding line is hard-coded. Workshop Booking already proposes a “05 · When I need to step in” category to fix it (Part II · c-stepin); this card's own PRD doesn't cite that category, so it isn't drawn here — flagged, not solved.
  • Holding line is hard-coded today ("I'm connecting you with {founder} who can assist you further. They'll respond shortly!").
  • Slack ping already wired, optional, ONE-WAY; the WhatsApp-thread reply path is always on.

Every booking lands in MystFlo's own ledger. Nothing flows outward — not to Sheets, not to Airtable, not to Notion, not to a file. And the founder's only view of that ledger shows bookings that are still unpaid — the moment one is paid, it leaves her screen; the Attendees block + sheet below fix that (re-cut 2026-09-02 — Peyton: the list lives IN the Pulse tab and rises as a nested sheet, no longer a standalone page).

  IN-CHAT (A · B) — v1's only mode

  Flow form submitted
        │ name · email · spots
        ▼
  ┌───────────────────┐
  │  MystFlo ledger   │   full_prepay → Mark paid is the billable event
  │  (cohort_bookings)│   free_rsvp → confirms on capture, no paid leg
  └─────────┬─────────┘
            ▼
  ┌───────────────────────────────┐
  │ Pulse · unpaid bookings only  │   paid → disappears from view
  └───────────────────────────────┘

        ✗ no write-back to any sheet        ✗ no Airtable / Notion
        ✗ no export                          (own-form / roster-sheet is
                                              Workshop Booking's v1 scope)
Design notes
  • Bookings flow straight into MystFlo's own ledger — name, email, spots, amount, status. It does not reach the founder's own tools, and today she cannot get it out.
  • No Sheets read path on this card — v1 is in_chat only. Airtable, Notion, and a plain export don't exist yet either.
  • Two paid-sources only: Mark paid (full prepay — her one-tap confirms) and free RSVP (confirms automatically on capture, no payment leg at all). No roster tick — that's Workshop Booking's own_form path, not this card's.
  • Every founder on this card has the same list shape: Pulse shows unpaid bookings until they're paid, then nothing, until Attendees (M1) ships.
Approved 2026-07-28 · re-cut 2026-09-02 (Peyton) · roster-first amendment 2026-09-03 (R37)Attendees — a block IN the right slot + a nested sheet; not a tab, no longer a page

Every field is already stored on the booking, and all three verdict actions already ship in the booking EF. Re-cut 2026-09-02 (Peyton ruling), amended 2026-09-03 (R37 — "right column as principle"): Attendees is a block that is the FIRST item in the Pulse tab's right slot (noun-titled, ≤5 rows, "See all N ›"), the booking link/share/flyer sit under it, and the full list rises as a nested, scrollable sheet over that tab — not a new tab (R30/R31 hold), not a full-width band, and no longer a routed page. The way in from the wall is a slot inside the Pulse card, not a row hanging under it. This supersedes the 2026-07-28 "Pulse row → full-screen destination" contract and the #2924 route lock; the shipped AssistantRecordsPage + RecordsEntryRow are now drift, not the contract. Deep link survives as a URL param on the sheet: ?assistant=<id>&records=open (param name = build decision, CTO); the old route becomes a redirect. Naming is per-assistant: Event shows Attendees, Checkout Orders, Waitlist Signups — one component, one records contract. "Who's coming" survives as the group heading inside. Event Booking (TMPL-EVBK01) rides these frames (design decision 2026-08-26) — this re-cut IS the Event Booking update; only the program nouns change.

NOT BUILTM1 shipped as a routed page (#2924) — now driftread + verdict, never editin-card slot → Pulse-tab block → nested sheet · R30/R31 unamended · R36not built · 2026-09-02
the post detail sheet (8b, Pulse tab) recedes behind — nestedBackdrop · ✕ / Escape close THIS sheet only · ?assistant=4L9&records=open · same sheet as master assistant-lifecycle.html #sec-9e
Attendees · Weekly sunset mixer
Desktop ≥1024 · 10 spots · 7 booked · 3 left · synced 2 min ago
✕
Awaiting paymentWaitlistFilterChipGroup — zero selected = everyone, no "All" chip
NameContactSpotsBookedStatus
Maya Chenmaya@studio.co226 Julpaid
Tom Reyestom@reyes.co126 Julpaid
Aisha Koneaisha@studio.co3link sent 27 Julawaiting paymentMark paid⋯
Devi Raodevi@rao.studio1free RSVP · 26 Julconfirmed
Waitlist · 2
Sam Oyelaranphone only—joined 27 Julwaiting
PaginationBar sortable — hidden at ≤1 page (20 rows/page); drawn here for shape only‹ PrevPage 1 of 1Next ›
Columns come from the per-assistant records contract — the same declaration that names the destination. The read-only columns match the mirror sheet 1:1, so in-app and her spreadsheet are the same picture. Read + verdict only — no cell is ever editable. Sheet chrome: ModalWrapper presentation="sheet", contentWidth="wide", nestedBackdrop, broadcastCloseAll={false}.
Story
  • As a founder on the morning of my event, I open the assistant and see exactly who is coming, who still owes me, and who's waiting for a spot — without opening a spreadsheet or scrolling WhatsApp.
Design notes
  • Lives on the assistant, never a global list — cross-assistant merged tables and phone/email identity joins are banned; deep-link entry required (event morning is repeat-entry) — now a URL param on the sheet (?assistant=…&records=open), not a route.
  • Nested sheet, not a page (2026-09-02). Rises over the Pulse tab the founder was reading; ✕ / Escape close only the sheet and she is back on the tab. Header band = noun · instance name, subtitle = spots · booked · left · synced-ago, utility ✕. Filter chips and pagination are the platform idioms (FilterChipGroup, PaginationBar), never a third style.
  • Every status stays listed — unpaid is called out by dot + verdict, never by being the only rows shown; waitlist rows group beneath (a spot opens → "who's next" is human-answerable).
  • Read + verdict, never edit — contact fixes happen in the founder's own sheet, returning through the narrow read-back enum.
  • The live sheet mirror is M2, opt-in at activation, never auto-created.
Approved 2026-07-28 · re-cut 2026-09-02Attendees — the way in (slot + block), and every state the populated frame doesn't draw

The frame above draws one populated table at one width. A founder meets six other screens before or instead of it: the in-card slot and the in-tab block that open it, the same rows on a phone (as a bottom sheet), the day-one empty list, the fetch in flight, the fetch that failed, and the deep link opened by someone who isn't signed in — or isn't the owner. Mobile leads. The 6-column grid is a lg 1024 structure only; below it the identical rows render stacked, and no column is dropped — every field stays visible, the layout changes, the data does not.

NOT BUILTnested sheet · deep-linkable via ?records=openone component, noun is a proprow geometry identical in every state
13a · The way in — a SLOT IN the Pulse card + a BLOCK IN the Pulse tab (not a row under the card, not a page, not a tab) not built · 2026-09-02
Active ▶⏸⏹ Details →
Weekly sunset mixer ASST-4L9
Event Booking
1 connected · next event Sat 10:00
Bookings
9 confirmed · 2 awaiting payment · $15.75 · 2h ago
Attendeeswho's coming, who still owes you7 booked · 3 left ›
Needs your attention
!
+1 ••• ••• 7742 — booked, not yet paid
Booked 44 min ago · payment ask sent, no reply yet
Nudge
375 · Pulse wall — the Attendees line is the card's LAST zone (after Runs, before any footer action); nothing hangs under the tile, so the wall stays a wall. Never renders for guests. Tap → detail sheet on Pulse + the records sheet.
Desktop · the in-card slot's two spot states, then the in-tab block
Attendeeswho's coming, who still owes you7 booked · 3 left ›
Attendeeswho's coming, who still owes yousold out · 2 waiting ›
Attendees 10 spots · 7 booked · 3 left · synced 2 min agoSee all 6 ›
Aisha Kone3 spots · link sent 27 Jul · aisha@studio.coawaiting paymentMark paid⋯
Maya Chen2 spots · booked 26 Jul · maya@studio.copaid
Tom Reyes1 spot · booked 26 Jul · tom@reyes.copaid
Devi Rao1 spot · free RSVP · devi@rao.studioconfirmed

4 of 6 · 2 on the waitlist · needs-verdict first, then newest. Full context: the Pulse tab in 8b.

Never amber-tinted as a whole row — sold-out is a state, not a fault. Status is a dot; the row stays monochrome. The block sits between the metrics and the activity feed (unified Pulse-tab template order: exception row → metrics · quick actions → records block → activity) and collapses entirely for a non-records assistant. No lantern on the block — an unpaid booking is pending (amber), a verdict is a domain action; D-5's "Waiting on you" flag keeps the tab's one lantern moment when present.
13b · Populated — the sheet below 1024 (375 bottom sheet · 768 shell), rows stacked
detail sheet dims behind · nested sheet
Attendees
10 spots · 7 booked · 3 left · synced 2 min ago
✕
Awaiting paymentWaitlist
Who's coming · 4
Maya Chenpaid
maya@studio.co · 2 spots · booked 26 Jul
Tom Reyespaid
tom@reyes.co · 1 spot · booked 26 Jul
Aisha Koneawaiting payment
aisha@studio.co · 3 spots · link sent 27 Jul
Mark paid⋯
Waitlist · 2
Sam Oyelaranwaiting
phone only · — spots · joined 27 Jul
375 · bottom sheet, full height · stacked rows — every column survives, none is dropped · pagination hidden at ≤1 page
detail sheet dims behind · nested sheet · rail 64px stays under it
Attendees
10 spots · 7 booked · 3 left · synced 2 min ago
✕
Awaiting paymentWaitlist768 · sheet takes the desktop band · content still stacked — the grid engages at 1024, not here
Who's coming · 4
Maya Chenmaya@studio.copaid
2 spots · booked 26 Jul
Aisha Koneaisha@studio.coawaiting paymentMark paid⋯
3 spots · link sent 27 Jul
Tablet takes its SHELL from desktop (the sheet's full-width band, rail still under it) and its CONTENT REFLOW from mobile (rows stay stacked) — the only thing extra width buys is the contact moving up onto line 1 and the verdicts moving inline.
13c · Empty — activation day, nobody has booked yet
detail sheet dims behind · nested sheet
Attendees
10 spots · 0 booked · 10 left
✕
◦
No one's booked yet

Your booking link is live. Share it and the first name lands here — usually within a day of posting.

Copy your booking link
375 · no chips — nothing to filter, so the control isn't drawn disabled, it isn't drawn
detail sheet dims behind · nested sheet
Attendees
Desktop ≥1024 · 10 spots · 0 booked · 10 left
✕
◦
No one's booked yet

Your booking link is live. Share it and the first name lands here — usually within a day of posting.

Copy your booking link
Column headers are NOT drawn over an empty body — a header row with nothing under it reads as a broken table, not as an empty one.
13d · Loading — the fetch in flight
detail sheet dims behind · nested sheet
Attendees
✕
375 · four skeleton rows at the real row height — never a centred spinner, never a height jump on resolve
detail sheet dims behind · nested sheet
Attendees
✕
NameContactSpotsBookedStatus
Column headers ARE drawn during load — they're structure the fetch can't change, and drawing them stops the header sliding in late. Screen readers get aria-busy="true" on the table; the shimmer is suppressed under prefers-reduced-motion.
13e · Error — the fetch failed
detail sheet dims behind · nested sheet
Attendees
10 spots
✕
!
Couldn't load your attendees

Nothing's lost — this is a connection problem on our end. Your bookings are safe.

Try again Close

Or ping us if it keeps happening.

375 · red is earned here — a fetch failure is a genuine failure, not a pending state · Close returns to the Pulse tab beneath
detail sheet dims behind · nested sheet
Attendees
Desktop ≥1024 · 10 spots
✕
last loaded 6 min ago
Couldn't refresh — showing the list from 6 minutes agoRows below may be out of date. Try again
Maya Chenpaid
Tom Reyespaid
Two error shapes, one rule: cold failure (no rows ever loaded) takes the full-surface state on the left; warm failure (a refresh failed over rows already on screen) keeps the stale rows, dims them, and names their age — never blanks a list the founder is reading. Verdict buttons are removed from stale rows: acting on data we know is stale is how a spot gets double-released. The in-tab block behind the sheet dims and strips its verdicts the same way — one hook, one stale flag.
13f · The deep link (?assistant=…&records=open), opened by the wrong person
◍
Sign in to see your attendees

We'll bring you straight back to this list.

Sign in
375 · NOT signed in — Pulse is a <ProtectedRoute>; sign-in first. Return-to back to the sheet is a build gap (no platform-wide return-to exists — named, not invented). Never a guest preview: a roster has no public shape.
Pulse
Your active assistants
ActiveDetails →
Her own assistant ASST-1A1
On-Brand Support
Runs
12 · 100% healthy · 1d ago · $1.10
375 · signed in, NOT the owner — ?assistant= resolves nothing she owns (RLS returns no row), so both params are dropped and Pulse renders her own wall. No sheet, no count, no name, no "permission" wording — byte-identical to a deleted assistant. Replaces the old in-surface "This list isn't here" state: there is no surface without the assistant.
Story
  • As a founder, whatever state the list is in — empty, still loading, broken, or not mine — I always know what happened and what to do next, and I never see a blank screen with no way forward.
Acceptance
  • NEW (PRD debt) — amendment pending, replaces the AC-WSBK-23 read — Given the in-card slot ("Attendees · N booked · M left ›"), the in-tab block's "See all", or the URL ?assistant=<id>&records=open / When resolved / Then the records sheet opens stacked over the post detail sheet's Pulse tab; ✕ and Escape close only the sheet; refresh, bookmark, second device reopen it. The post-activation tab set stays Pulse · Overview · Personalize · Connect. (AC-WSBK-23's "same full-screen destination" wording no longer matches — never edited here.)
  • AC-WSBK-24 ↗ — paid rows stay listed; unpaid are called out by dot + verdict, never by being the only rows present.
  • AC-WSBK-25 ↗ — the only controls on a row are mark paid · extend · release; no state adds an editable cell, and the stale-rows error state removes verdicts rather than adding any.
  • AC-WSBK-27 ↗ — waitlist rows group beneath attendees in every tier; on 375 they keep the same stacked row shape, Spots reads "—".
  • AC-WSBK-28 ↗ — a paused assistant renders these states unchanged; pausing is not an error state.
  • NEW (PRD debt) — amendment pending, replaces the AC-WSBK-31 read — Given a signed-in non-owner opens the deep link / When ?assistant= resolves no owned assistant / Then Pulse renders their own wall with no sheet, no count, no name, no permission wording — byte-identical to a deleted assistant. (AC-WSBK-31's "This list isn't here" state no longer exists as a surface.)
  • NEW (PRD debt) — Given the attendee fetch fails after rows have already rendered / When the founder is looking at the list / Then the stale rows stay on screen, dimmed and labelled with their age, with verdict controls removed — the list is never blanked and a stale row is never actionable.
  • NEW (PRD debt) — Given a signed-out visitor opens the Attendees deep link / When the route resolves / Then they are sent to sign-in with a return-to back to this list — the surface has no guest or preview mode.
Design notes
  • Nested sheet, not a routed page (Peyton 2026-09-02 — supersedes the "Routed page, not a slide-over" bullet and #2924). The list rises over the Pulse tab as a ModalWrapper sheet (nestedBackdrop, broadcastCloseAll={false}, contentWidth="wide"), so the founder never loses the Pulse context — ✕ returns her to the tab. The deep-link reason that justified the route survives as a URL param on the sheet (?assistant=<id>&records=open; name = build decision, CTO): bookmark, refresh, second device all reopen it. The old /pulse/:assistantId/records route becomes a redirect. Still not a PageSlideOver — a data-review-and-decide panel is a sibling sheet (2026-07-01 lock).
  • Structure steps at lg 1024 only (6-column grid; below it the same fields stack, nothing dropped); row geometry identical across all four data states (56px min mobile / 40px grid) so the surface never jumps height on resolve. Fluid per U8; radius/hairlines constant.
  • Status is a dot, the row stays monochrome — emerald paid (C12), amber awaiting/sold-out, white/35 waiting. Red appears only on a genuine load failure, never on an unpaid booking.
  • One component, three nouns. Attendees / Orders / Signups is a prop plus a per-assistant column contract; every state above is authored once and reused. The empty-state copy is the only per-assistant string.
  • Build gaps (2026-09-02): route redirect + retire AssistantRecordsPage · in-card slot in AssistantSummaryCardPost + retire the RecordsEntryRow mount · records block in the post modal's Pulse tab · RecordsSheet on ModalWrapper over AssistantModalShell (z-index above the shell) · warm-stale flag shared by block + sheet · ?records=open persisted like ?assistant=.
What the waitlist actually stores
  • ✓ A real row IS written — name, phone, joined-at, subscribed status.
  • ✗ Email is collected and validated, then written email: null bug — discarding the one channel not rate-limited by WhatsApp's 24h window.
  • ✗ Rows are tagged as campaign overflow — a different assistant's concept reporting.
  • ✗ Spot cap exists ONLY on the Flow-form path overbooking — typed capture (what every instance runs until Meta approves its Flow) has no spot check; a 10-spot event can confirm 15.
Fallback “what happens when Meta says no?” · sec-6b template rejected · sec-6c event details didn't save
!
WhatsApp turned down your night reminder

Bookings and payment relay keep working. Only the reminder is blocked until the wording is fixed.

What they flagged "Too promotional for a utility message." WhatsApp is strict about wording on automated reminders sent outside the customer's active reply window.
Rewrite and resubmit
Use our approved wording

Resubmitting goes back into review — same one-to-two-day window.

Frame 6b · Template rejected [Fallback]
  • Story — As a founder, if Meta rejects a template I get the exact reason and two ways to fix it — while bookings and payment relay keep running underneath.
Acceptance
  • AC-WSBK-19 ↗ — a rejected template shows a named, actionable fix path (rewrite & resubmit, or use MystFlo's approved wording), never a dead end; other capabilities (capture, payment relay) stay unaffected.
Design notes
  • A real state Event Booking can hit, unlike On-Brand Support: On-Brand Support has no WhatsApp template dependency and correctly draws no rejected-template state. Event Booking depends on Meta approving two templates (confirmation + reminder), so rejection is a real outcome — design-canon's D-3 rule requires red-status failure states to always show a named fix path, never a dead end. Modeled directly on the master's own rejected-template twin (assistant-lifecycle.html frame 7e), adapted to this assistant's two templates.
  • Deliberately NOT drawn here: the generic live-test rollback (B6 — WhatsApp connection failure, unrelated to template wording) already covered once by the master contract. This frame is the template-specific twin only.
!
Your event details didn't save

Your assistant is live. Bookings and reminders are paused until your event details save.

What happened Your event details didn't save. Your WhatsApp, templates and payment setup are fine. Try again.
Try saving again
Check the details first

Pauses the assistant and opens Personalize so you can fix the details there. Bookings and reminders start the moment they save.

Frame 6c · Your event details didn't save [Fallback]
  • Story — As a founder, if the event details behind my assistant don't save, I'm told plainly and I can retry in one tap — never a silent "live" that quietly can't book anyone.
Acceptance
  • Not yet built — filed as #4634. Today activate-assistant treats a failed seedWorkshopCohort as non-blocking (index.ts:1915-1918) and the founder sees the ordinary Frame 6 success screen with no cohorts row underneath — no bookings, no reminders, no signal. This frame is the target state once #4634 (B2) forwards a cohortSeedFailed flag the same way workshopTemplatesPending/workshopTemplatesFailed already ride the activation response.
Design notes
  • Card shape reused verbatim from Frame 6b (template rejected): same .fail-badge, same .notice block ("What happened" instead of "What they flagged"), same stacked primary/outline buttons, same fine-print line — no new card style, colour, or icon introduced. Red badge because this blocks the founder's actual capability (bookings), not just one channel — a stricter case than 6b's reminder-only block.
  • Replaces Frame 6 entirely — the founder never sees the green checklist over an event that isn't actually bookable, matching how 6b fully replaces 6 rather than layering on top.
  • Desktop drawn here; 375 is the one exception carved out of the Monitor phase's mobile tier below (#sec-try-seed-failed-m) — 6a/6/8/6b stay desktop-only since nothing else in this phase changes shape at 375, but this is the fix path a founder is most likely to hit first, right on their phone, right after tapping Activate.
  • On a second failed retry: same card, same copy — only the fine print changes to "Still not saving. Pause and edit your event details, or message us" with a real Message us link (wa.me/<MYSTFLO_SUPPORT_NUMBER>, config value, never hardcoded, run reference prefilled — same pattern as the master's assistant-lifecycle.html sec-7 "(b) Failed" sub-frame); no new panel, the card just updates in place.
  • Successful retry returns straight to Frame 6 (sec-6) — Frame 6 IS the confirmation; no separate success card is drawn (canon U13: state changes are one-shot, not a new persistent screen).
  • Never shows an error code or blames the founder — matches this file's D-3 fallback tone (6b) and the platform-wide error copy standard (honest, helpful, calm).
RECURRING SURFACE · Pulse hand-back, drawn once in the master
Dismissing 6c doesn't dismiss the problem: the assistant is still live with no bookable event underneath, so the exception carries into Pulse's Needs your attention inbox (master 8c) as a standing row until it's fixed — the founder is never left with no way back to the fix.
assistant-lifecycle.html #sec-8c ↗
  • Row copy: “Your event details didn't save · Retry” — same .att-row grammar (! icon · title · outline button) this file's own sec-8 already draws for the pending-templates row; tapping Retry reopens 6c.
768 not yet drawn for this flow. Desktop and 375 are the contract.
6a/6/8/6b use Desktop. No 375 drawing for those — 6c below is this phase's one exception.
!
Your event details didn't save

Your assistant is live. Bookings and reminders are paused until your event details save.

What happened Your event details didn't save. Your WhatsApp, templates and payment setup are fine. Try again.
Try saving again
Check the details first

Pauses the assistant and opens Personalize so you can fix the details there. Bookings and reminders start the moment they save.

Same content and copy as the desktop draw (#sec-try-seed-failed) — story, acceptance, and design notes are not repeated here.
8b · Pulse tab — RECURRING SURFACE, drawn once in the master R30 · R37 ↑ top
Revisit · 2026-09-04, corrected same day (#4432 Track 2 → GAP B) — header only rides the master; Personalize is drawn here, not thereEvery header below now carries the shared four-line hierarchy (assistant-lifecycle.html frame 9g / 13g: subject · “Event Booking Assistant · id” · status · price · live since · activity; no health line, no repeated type word) — THAT part is drawn once in the master. Correction: an earlier pass here also pointed the post-activation Personalize tab at master frame 9c/13c as "one drawing, one contract." That undersold a real difference: Event's own Personalize surroundings (Price per ticket/Currency/Party size, no 03b where-guests-book choice) don't match Workshop's (Price per seat/Total seats, a real 03b radio) closely enough for the master's literal field values to stand in for this card. Personalize now gets its own frame — 8c ↓ — using the SAME locked-mirror skeleton and treatment the master's 9c established (categories, .pz-locked on the form column only, preview column carried over identically from pre-activation, nothing clickable), with Event's own field labels and values.
RECURRING SURFACE · drawn once in the master
Event Booking's Pulse tab is the master's BOOKING (records) fill of the ONE Pulse-tab template — same instance (Weekly sunset mixer · ASST-4L9) the master already draws, roster FIRST in the right slot.
assistant-lifecycle.html #sec-9b-r ↗
Twins rule (shell-language #7) and the R28/R29/R30 Overview/Personalize/Connect notes carry over unchanged from the prior drawing — see this file's own sec-2 (Overview) and sec-5 (Try/read-back) for the source copy the post tabs mirror word-for-word. sec-13 keeps the full records-sheet columns contract; its caption points at master frame 9e.
8c · Post detail sheet — Personalize tab — the locked mirror, Event Booking's own fields A6 presentation · #4432 GAP B · 2026-09-04 ↑ top

Same locked-mirror skeleton and treatment as the master's assistant-lifecycle.html 9c/13c: same .sm group headings, .field-label/.field/.opt-radio row anatomy, .pz-locked wash on the form column only (never the preview), nothing clickable. Not pointed at the master this time — Event's own Personalize surroundings differ from Workshop's closely enough (below) that the master's literal field VALUES can't stand in for this card. Drawn on the Weekly sunset mixer instance (ASST-4L9) already live in this file's own Pulse frames (8, 8b) — 10 spots, live weekly, $1.75 per run.

Desktop 1180 · ACTIVE — at rest
header is the master's shared four-line hierarchy (9g) — not re-derived here
Weekly sunset mixer✎
Event Booking Assistant · #4L9
Active · $1.75 per run · live since Aug 5
Booked 2h ago
▶⏸⏹⋯✕
Pulse Overview Personalize Connect

This is what's set for this round — nothing here can be edited in place. To change any of it, start fresh with a new assistant → (the ⋯ menu, top right). Brain readiness lives on Connect →

01 · Your event
Name
Weekly sunset mixer
Price
42
Currency
USD
Date
Tue Sep 8, 2026
Time
18:00–21:00
Labels follow the shared offering vocabulary (Peyton 2026-09-23) — the mirror shows what Personalize showed. When the range’s end is stale (at or before its start), the box keeps its value and adds this note: This is set before the start time. (ConfigFieldRenderer.tsx read-only mirror.) Timezone and Where are not yet drawn on this mirror — pre-#4478 crop.
Spots
10
02 · How guests pay

Both options still show — the picked one just reads selected, not tappable.

Full prepay
Guest pays in full to hold the spot.
Free RSVP
No payment — just a headcount.
03a · Where guests reach you
Booking link
wa.me/15551234567?text=book
04a · Booking messages
Booking confirmation locked
Meta-approved · not editable
Header: "You're booked — Weekly sunset mixer"
Hi Maya, your spot for the Weekly sunset mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
04b · Payment message — added 2026-09-04, confirmed live on this card
How guests pay

All three options still show — the picked one just reads selected, not tappable.

Card on your ticketing page
Guests pay on the page you already use.
Stripe payment link
A Stripe payment link you already have. MystFlo relays it — it never takes the money.
QR code
Paste a public link to your QR image. Guests get it with the payment message.
Payment link
https://buy.stripe.com/8wM4kb…
Payment ask — the founder's own wording, verbatim
Your spot's held — it's yours the moment I see your payment. Reply here once it's sent and I'll confirm.

Runs on demand — whenever a guest messages this number.

·· preview — “In this chat” · once your Flow is live ··
hi! any spots left for the mixer?
Beach Sunset Mixer
· Jul 28, 2026
· 18:00–21:00
· The Offline Club, 88 King St W
Tap below to grab your spot:
When a photo is set (Photo field, above), this SAME bubble sends with the photo as its image header:
photo header · 1.91:1
Beach Sunset Mixer
· Jul 28, 2026
· 18:00–21:00
· The Offline Club, 88 King St W
Tap below to grab your spot:
Beach Sunset Mixer
Tue 28 Jul · $42 per ticket · 3 spots left
Name
Email
Party size
2026-07-28
Confirm booking
Real form fields after activation — never faked here
Your details are in, Maya. Your spot for Beach Sunset Mixer is held — it's confirmed the moment your payment lands.

Here's how to pay for your spot — reply once you've sent it and I'll confirm.
↗ Pay now
When “QR code” is picked (04b), this bubble sends instead:
your QR as an image — the same payment-ask words as its caption
done — just sent it!
you tap Mark paid
You're booked — Beach Sunset Mixer
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
Then: the Night reminder (04a) — same thread, 1 day + 1 hour before it starts

Works the same whether they tapped your wa.me link or typed this directly — the AI reads intent, not just the word. No second, dimmed “own booking form” preview here — v1 has only this one path.

375 · ACTIVE — at rest
dimmed Pulse peeks — tap to return
Pulse Overview Personalize Connect

This is what's set for this round — nothing edits in place. Start fresh with a new assistant → (⋯, top right). Brain readiness lives on Connect →

01 · Your event
Name
Weekly sunset mixer
Price
42
Currency
USD
Date
Tue Sep 8, 2026
Time
18:00–21:00
Shared offering labels (Peyton 2026-09-23). When the range’s end is stale, the box keeps its value and adds this note: This is set before the start time.
Spots
10
02 · How guests pay
Full prepay
Guest pays in full to hold the spot.
Free RSVP
No payment — just a headcount.
03a · Where guests reach you
Booking link
wa.me/1555…?text=book
04a · Booking messages
Booking confirmation locked
Meta-approved
You're booked — Weekly sunset mixer. Hi Maya, your spot is confirmed.
04b · Payment message
How guests pay
Card on your ticketing page
Guests pay on the page you already use.
Stripe payment link
MystFlo relays it — it never takes the money.
QR code
A public link to your QR image.
Payment link
https://buy.stripe.com/8wM4kb…
Payment ask
Your spot's held — it's yours the moment I see your payment. Reply here once it's sent and I'll confirm.
·· preview — “In this chat” · once your Flow is live ··
hi! any spots left for the mixer?
Beach Sunset Mixer
· Jul 28, 2026
· 18:00–21:00
· The Offline Club, 88 King St W
Tap below to grab your spot:
When a photo is set, this SAME bubble sends with the photo as its image header (see desktop frame).
Beach Sunset Mixer
Tue 28 Jul · $42 per ticket · 3 spots left
Name
Email
Party size
2026-07-28
Confirm booking
Real form fields after activation — never faked here
Your details are in, Maya. Your spot for Beach Sunset Mixer is held — it's confirmed the moment your payment lands.

Here's how to pay for your spot — reply once you've sent it and I'll confirm.
↗ Pay now
When “QR code” is picked (04b), this bubble sends instead:
your QR as an image — the same payment-ask words as its caption
done — just sent it!
you tap Mark paid
You're booked — Beach Sunset Mixer
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
Then: the Night reminder (04a) — same thread, 1 day + 1 hour before it starts

Works the same whether they tapped your wa.me link or typed this directly — the AI reads intent, not just the word. No second, dimmed “own booking form” preview — v1 has only this one path.

Tablet 768: not drawn — this flow's own contract is desktop + 375 only (see sec-3b/sec-3 above); undrawn here for the same reason, not invented.

Story
  • As a founder opening Personalize on my live event assistant, I see the exact form I filled out at signup, in my own words — nothing shouting "locked", nothing inviting a tap. One line at the top tells me the only way to change it.
Acceptance
  • Shares the master's AC-LIFECYCLE-11/12 ↗ (nothing edits in place in any status; the only door is "Start fresh with a new assistant") — not re-cited in full.
  • Why there's no "03b · Where guests book" section here (correction of an earlier brief for this same gap): Workshop Booking's booking_surface field genuinely offers two options (in_chat/own_form), so its locked mirror shows both, one selected. Event Booking's booking_surface field has the SAME key name but only ONE option (in_chat) — the migration strips own_form outright (supabase/migrations/20260813130000_evbk01_event_booking_card.sql:476, DELETE ... WHERE f->>'key'='booking_surface' AND o->>'value'='own_form') and eventBookingCategories.ts never even reads the key (no findField(fieldByKey, 'booking_surface') call in its regroup function) — there is no choice to render, locked or otherwise. This file's own pre-activation sec-3/3b already says so explicitly ("v1 is in_chat only — no 'where guests book' radio") and its AC note (AC-WSBK-17 ↗) says the same field-parity criterion "only partly applies — the payment half, not the surface half." A same-day brief for this gap first described the two cards as sharing an identical two-choice Personalize skeleton; checking the schema directly (above) shows that's true for payment, not for booking surface — corrected here rather than drawing a fictional second option.
  • 02 · How guests pay fills the slot 03b would have used — same real-choice-question shape the master's 9c reserved for Workshop's 03b, just Event's own actual choice field (payment_mode: full_prepay/free_rsvp).
Design notes
  • Skeleton and treatment copied from the master's 9c, field content is this card's own. .pz-locked sits on .form-col only — never on .pv-col — per Peyton's same-day ruling that the preview is a static design visualisation, identical pre- and post-activation ("just have it stay the same"). The .pv-col here is byte-for-byte this file's own pre-activation preview (sec-3b desktop / sec-3 mobile) — same Beach Sunset Mixer illustrative sample, same "once your Flow is live" copy, not restyled, not re-derived from the instance's own saved values (Weekly sunset mixer) — same rule the master fix applied, see assistant-lifecycle.html #sec-9c ↗ for the full rationale.
  • Instance picked, not invented from nothing: "Weekly sunset mixer" / ASST-4L9 is this file's OWN established live instance (sec-8 Pulse card, sec-8b Attendees) — reused here rather than minting a fourth sample name for the same card (this file already runs three: the illustrative "Beach Sunset Mixer" in sec-3/3b/7/9, the live "Weekly sunset mixer" in Pulse, and now this frame reuses the live one for internal consistency with sec-8/8b).
  • 04b — added 2026-09-04, corrected from an earlier "skip it, matches master" call. A first draft of this frame omitted the payment_method radio entirely, reasoning that the master's own 9c also skips Workshop's equivalent field. Checked against the real question — "does this field render pre-activation?" — and it does: eventBookingCategories.ts's evbk_payment_message group ("04b · Payment message") renders payment_method + payment_link + copy_payment_ask together, and the field is REAL and shipped (supabase/migrations/20260814121000_booking_payment_vehicle.sql, applied to BOTH TMPL-EVBK01 and TMPL-WSBK01, 2026-08-14). Drawn from that migration's actual current labels/options ("How guests pay" — Card on your ticketing page / Stripe payment link / QR code), not from this file's own pre-activation sec-3b/sec-3 — those predate the Aug 14 migration and still show an older, stale two-option "Payment method · not built · #3153" version (Payment link / QR only). "Payment ask" moved here from 04a to match its real category (it was drawn under "04a · Booking messages" in the first pass, copying master 9c's own mislabeling of the same field).
  • Flagged, not fixed here — three surfaces are stale against the same Aug 14 migration: this file's own pre-activation sec-3b (desktop) / sec-3 (mobile), workshop-booking.html's equivalent sec-3b/sec-3, and the master's own 9c (which still omits Workshop's 04b payment-method radio from its locked mirror the same way this frame's first draft did). All three should get the same fix this frame just got — real 3-option field, current labels — but that's a wider, separate pass across files this session isn't scoped to make unilaterally.
  • Booking confirmation template substitutes the instance's REAL saved event name ("Weekly sunset mixer") for the illustrative sample's ("Beach Sunset Mixer") — a locked Meta template renders from the founder's actual saved values at send time, so the mirror should too, even though the preview column beside it (a static illustration) keeps the sample name unchanged. These two panels intentionally show different event names for different reasons — flagged so it doesn't read as a copy-paste mismatch.
RETRACTED — no schema drift
  • An earlier version of this note claimed payment_method (Workshop) and payment_mode (Event) were the same booking-choice concept under two drifted key names. Wrong — they're two legitimately distinct fields both cards carry: payment_mode is the payment STRUCTURE (full_prepay/free_rsvp — in both the WSBK01 and EVBK01 card migrations), payment_method is the payment VEHICLE (ticketing_page/stripe_link/qr — added to BOTH TMPL-EVBK01 and TMPL-WSBK01 by the shared supabase/migrations/20260814121000_booking_payment_vehicle.sql). No unification work needed.

Spec: prd-as-event-booking.md (field source: eventBookingCategories.ts + the EVBK01 migration). Archetype: assistant-detail-tabs.md § 2026-09-04 locks. Master pattern: assistant-lifecycle.html #sec-9c / #sec-13c-m.

Guest
This step is 375. No 1180 drawing — use the Mobile size.
768 not yet drawn for this flow. Desktop and 375 are the contract.
Guest journey — Trigger · Interact · Outcome what the guest sees on WhatsApp · sec-7, forked on booking_surface

Every bubble below draws the in_chat branch — v1's only path. A guest DMs, the assistant captures name/event/headcount, and relays the founder's ticket link. When the founder's Flow is published, this typed capture is replaced by the native in-chat form — drawn in the Secondary sub-frame below (payment still relays outside the form; the US has no in-Flow payment). There is no own_form branch to draw here — that guest path is Workshop Booking's v1 scope, not this card's.

Booking capture — before/after the copy-mismatch fix [Trigger → Interact, in_chat]
Current — retreat-flavored (wrong)
Proposed — event-flavored (fixed)
Sub-frame · Booking capture
  • Story — As a guest, I ask about an event in the founder's own voice and get an answer about MY event — not a retreat cohort I never signed up for.
Acceptance
  • AC-WSBK-01 ↗ — the capture step (name, session pick, headcount) is what creates the pending_payment booking row this reply represents.
Design notes
  • Confirmed, not hypothetical — for Workshop Booking; presumed shared here: the shared booking engine's live AI prompt is worded for retreats, not events, per Workshop Booking's own verification (it says "our retreat," asks for "name, email, how many spots," and treats the guest as "joining a retreat cohort"). Since this card wears the same engine, the same defect plausibly reaches Event guests too — but that has not been independently verified against Event's own live prompt; confirm before treating it as shipped fact for this card.
  • Email stays in the fixed bubble too — it isn't a stylistic choice. The real capture gate (shouldCaptureCohortBooking, shared by this booking engine) requires name AND email before a booking is persisted or the payment ask is shared; only the retreat-vs-event wording changes — the three-field capture contract itself is unchanged and correct.
Payment relay — before/after #2796 [Interact, in_chat]
Current — broken (#2796)
Proposed — fixed
Sub-frame · Payment relay
  • Story — As a guest, once I say I'm in, I get the founder's real payment link right away — not a hand-off promise that goes nowhere.
Acceptance
  • AC-WSBK-01 ↗ — "the assistant relays the host's payment ask" is this criterion's own language; #2796 was this exact promise silently failing in the in-chat runtime.
Design notes
  • Root cause of #2796: copy_payment_ask is bridged to cohorts.payment_instruction only at activation, and only the WA-Flows path reads it at runtime — the default in-chat path never does, so the AI falls back to a generic hand-off line instead of relaying the real link. The proposed fix makes the in-chat runtime read the same field, closing the gap.

The two locked, Meta-approved template bodies (Booking confirmation · Night reminder) live at full size in Part II · Components — seated in the flow at Personalize 04a (sec-3/3b) and in every sec-9 thread.

Secondary — the WhatsApp Flow spot-booking form [Interact, alternate rendering]
Sub-frame · Secondary WhatsApp Flow form
  • Story — As a guest, when the founder's Flow is published, I book through a native in-chat form instead of typing my details one message at a time.
Design notes
  • When the Flow is published, guests get this native in-chat form instead of typed capture; when not published, capture falls back cleanly to plain chat.
  • The intro line is a Personalize field, editable, same mechanism as Payment ask — not the runtime's hardcoded string. Approved 2026-07-28. Default reseeded 2026-08-21 (#3789, CPO): "{{workshop_name}} — {{date}}. Tap below to grab your spot:" — the invite names the program + date before the Book-now button; {{program}} aliases {{workshop_name}} (the Event card's seeded token now resolves), and a missing date degrades to the name-only line, never a literal token or a blank.
  • What triggers this form is not literal keyword matching — it's the AI's own classification of the message's intent, so free-form text ("any spots left tuesday?") triggers it exactly like a link-tap's pre-filled "book" does. The conditional "Tell assistants apart" keywords field (Personalize 03a) is a separate, earlier-stage mechanism: it only disambiguates which assistant an inbound message routes to when a founder runs more than one WhatsApp assistant on the same number.

Section 7 shows individual messages being fixed. This section shows the whole thread, unbroken, for every mode a founder can pick — and, beside each thread, exactly where every message's words come from and where the founder meets them in Personalize and the live preview. One card per mode; nothing here is invented — every bubble traces to a named template, config field, or the real runtime string, and anything not built lives in Part III · Future, never here.

First: one surface, one payment fork — not a 2×2

Unlike Workshop Booking, there is no “where guests book” setting here — v1 is in_chat only. The only real fork is payment structure: a founder picks full_prepay or free_rsvp. That's two shipped modes, plus Mode D (concurrent events on one number) and Mode E (not built).

  payment structure (in_chat is the only surface — no own_form fork)
        │
        ├── full_prepay  (default) ──►  MODE A — shipped
        │
        ├── free_rsvp ────────────────►  MODE B — shipped
        │
        └── deposit_balance ── hidden on this card — a retreat concept, lives on Retreat Booking

  ┌ MODE D — 2+ booking cards live on ONE WhatsApp number. Supported product usage ──┐
  │ (a founder running concurrent programs), not a misconfiguration. "what's on?"    │
  │ or a bare "book" → deterministic name+date list; a unique full program name      │
  │ routes straight to that card's form. SHIPPED (#3789) — drawn below.              │
  └─────────────────────────────────────────────────────────────────────────────────┘

  ┌ MODE E — guest DMs and the AI already reads booking intent. What's missing is ───┐
  │ the CHOICE within ONE assistant: offering its several open events to pick     │
  │ from. NOT BUILT — two real blockers, drawn in Part III · Future.                 │
  └─────────────────────────────────────────────────────────────────────────────────┘
meta template locked, Meta-approved — founder reviews, never edits founder field a real Personalize input, sent verbatim ai-composed written live in the founder's voice from the Brain native form WhatsApp's own UI, not text
Mode AFull payment, booked in this chat

The default, and the one most founders will run. The guest never leaves WhatsApp; the money does — payment happens on the founder's own link, because WhatsApp's in-form payment is India/Singapore only.

payment_mode = full_prepay booking_surface = in_chat shipped
Story
  • As a guest, I ask about an event, fill a short form, pay on the founder's own link, and get a confirmation and two reminders — all in one thread, without ever being handed off.
Acceptance
  • AC-WSBK-01 ↗ — capture creates the pending_payment row and the assistant relays the founder's payment ask.
  • AC-WSBK-02 ↗ — confirmation within 30s of payment being learned. AC-WSBK-03 ↗ — reminder fires at both offsets.
Design notes
  • Message sources, 1→7: 1 guest (intent-classified — wa.me only pre-fills "book") · 2 invite = copy_flow_intro founder field (default names the program + date, #3789) over WhatsApp's native form (Event's own persona-worded Flow (event_booking Meta name, “Reserve my ticket” — hard-gated at activation per the PRD's WhatsApp templates table); unpublished → typed capture, nothing waits) · 4 = ONE message, thank-you first (runtime string, composePaidBookingThankYou — "spot is held, confirmed the moment your payment lands", P2: pay AFTER the form), then the founder's Payment ask (Brain policies.payment wins when set); a single URL in the ask is lifted onto a native Pay now button (sendWhatsAppCtaUrl) · 5 confirmation = event_booking_confirmation template, ≤30s after Mark paid · 6/7 = ONE class_reminder body, both offsets.
  • Capture gate: name + email (shouldCaptureCohortBooking) — no row without both; spots re-checked at submit, not send. Mark paid is the ONLY door to confirmed on this mode — the assistant never takes the guest's word.
  • Four kinds of words in one thread (runtime string · founder field · Meta template · live AI) — the source-chip vocabulary is proposed to carry INTO Personalize so "why can't I edit this one?" answers itself.
  • The only unbounded gap is 3 → 4 → Mark paid — nothing chases an unanswered payment link; see Part III · F2. Not drawn as solved because it isn't.
Mode BFree RSVP, booked in this chat

Same thread as Mode A with the money taken out — and taking the money out removes two steps, not one. There is no payment ask, and no Mark paid: the booking confirms itself the moment the form comes back.

payment_mode = free_rsvp booking_surface = in_chat shipped
Story
  • As a guest at a free session, I say I'm coming, fill in three fields, and I'm confirmed on the spot — nobody asks me for money and nobody makes me wait.
Acceptance
  • AC-WSBK-05 ↗ — a free_rsvp event skips the payment-ask leg entirely and goes straight to confirmed.
Design notes
  • Delta from Mode A: trigger/form/submit identical (form softens to "Save your place", price/date field hidden — same Flow JSON, one conditional field); payment ask AND Mark paid legs removed — captureCohortBooking calls learnPaid on submit, same meter, same exactly-once; no "awaiting payment" state ever appears in Pulse.
  • P3 revised (#3789, CPO Option B): bubble 4 is an in-window session message (composeFreeBookingConfirm — "you're in / free / see you {date}"), NOT the locked Meta template. learnPaid runs with skipWhatsApp (honored only for free_rsvp) — it still meters, emails, and confirms; the locked event_booking_confirmation template is reserved for Mark paid on the paid card. Typed capture sends the same session confirm; an already-confirmed retry never re-texts the guest.
  • Proposed proposed: the "Payment ask" field hides when Free RSVP is picked — an editable sentence that never sends is the dead-control problem this rescope removed five toggles for.
  • Reminders unchanged — they key off cohorts.start_date, not payment. Free RSVP is the one mode needing no nudge machinery (no unpaid state to chase).
Mode DTwo live cards, one number — who answers?

Several booking cards Active on one WhatsApp number is supported product usage — a founder running concurrent programs — not a misconfiguration. Every booking link prefills the same generic keyword by design, so when the message doesn't uniquely name a program, the assistant answers with a deterministic list instead of guessing — never the wrong Flow, never Brain improvisation.

2+ booking cards · one WhatsApp number any payment_mode shipped · #3789
Three deterministic rules — never a guess
1
Unique full program name → that card's form router
"book Beach Sunset Mixer" — or the name inside a what's-on ask — opens that card's Flow directly, no list. A single stray word ("friday") is never enough: keyword-free unique match requires the full program name, so it can't steal the turn from On-Brand Support.
2
"what's on?" or a bare "book" → the name+date list runtime string
Bubble 2 is composeBookingCatalogReply — sorted by start date then name, one bullet per live card. Deterministic, not AI-composed: two cards can't be told apart by the shared "book" prefill, and guessing sent an Event guest into a Workshop Flow (live test, 2026-08-20).
3
Ties break the same way every time router
Sibling names sharing a word tie-break by longer (more specific) name, then most-recently-created — every input order resolves identically (resolveBookingPhase0, whatsapp-routing.ts).
Story
  • As a guest texting a studio that runs two programs at once, I ask "what's on?" and get a short, real list with dates — I name the one I want and the right booking form appears, never the wrong one.
Design notes
  • The design story is the concurrent one. QA walks that stop one card before testing another are a test constraint (isolation), not the product: the router exists precisely so founders can keep several cards live on one number.
  • Delta from Mode E (F1): this is 2+ cards on one number, each still one program — shipped. Mode E is one assistant holding several events itself — still not built, and still On-Brand Support's job (see F1).
  • iPhone curly apostrophes in "what's on?" are normalized before matching — the ask can't miss on typography.
Every modeWhen the guest asks something else

This is already built, and the answer to "route to the owner or swap in the on-brand assistant?" is both, in that order — the handover is automatic and the founder never configures it.

shippedchat-handler.ts · fall-through gate
  guest asks something off the booking script
                 │
                 ▼
      ┌ can Event Booking answer it? ┐
      │  from the event details or   │──── yes ──► answers, thread continues
      │  the Brain                      │
      └────────────┬────────────────────┘
                   │ no  (intent = UNSURE)
                   ▼
      ┌ is On-Brand Support live on ────┐
      │  this same WhatsApp number?     │──── no ───┐
      └────────────┬────────────────────┘           │
                   │ yes                            │
                   ▼                                │
      ┌ Support takes one more shot with ┐          │
      │ the full Brain + FAQ, and the    │── fails ─┤
      │ answer is fact-checked before    │          │
      │ it is allowed out                │          │
      └────────────┬─────────────────────┘          │
                   │ passes                          ▼
                   ▼                    ┌ founder gets the thread ─────┐
            guest gets a real           │ holding line to the guest ·  │
            answer — no handoff         │ human mode on · host pinged  │
                                        └──────────────────────────────┘

  "get me a human" / refund / date move ─────────────────────────► straight to founder,
                                                                    no Support attempt
Design notes
  • The ladder, A→E: answerable → answered in the founder's voice, thread continues · unanswerable → On-Brand Support gets ONE attempt, fact-checked before it can reach the guest · refunds / date moves / "get me a human" → straight to the founder, honest holding line, human mode (the assistant never tells a paid guest their booking is cancelled) · "I already paid!" → acknowledged, never believed — confirmed only when payment is learned · sold out → hard-coded waitlist line; spots re-counted at submit.
  • Nothing here needs building — it needs showing — the ladder is live and invisible; a Try-step / Explore-copy opportunity, not engineering.
  • The real gap: the holding line and the sold-out line are hard-coded — the two messages a founder most wants in her own words, and neither appears in Personalize (holding line now proposed as Personalize 05).
Master contract + sources
  • assistant-flows/assistant-lifecycle.html is the primary structural contract; assistant-flows/on-brand-support.html is the sibling exemplar this file borrows its token block and component classes from verbatim.
  • Sources: prd-as-event-booking.md §Test Plan (AC-WSBK-* / AC-BILL-*) · DB migration 20260813130000_evbk01_event_booking_card.sql · ConfigureStep.tsx isBookingFamily gate · no E2E coverage (tests/e2e/assistants/tmpl-evbk01.spec.ts was never written — verified absent 2026-09-05, #4476).

Business name ("The Offline Club Studio"), guest names, and phone numbers are illustrative. This file draws the proposed target.

Part II — Components the parts library at full size — reference only, nothing here is the ONLY place a thing appears (kit lock 2026-07-30).
C1 · Locked, Meta-approved templates ↑ top in the flow ↑
Meta-approved · lockedBooking confirmation
Header: "You're booked — Beach Sunset Mixer"
Hi Maya, it's Priya — your spot for the Beach Sunset Mixer is confirmed. We'll send a reminder before it starts. Reply here with any questions.
Meta-approved · lockedNight reminder — one body, both offsets
Hi Maya, it's Priya — just a quick reminder, your spot's all set. If anything comes up, reply here and let me know. See you soon!
C1 · Locked templates seated: Personalize 04a (sec-3/3b) · every sec-9 thread
  • Story — As a guest, once I've paid I get a real confirmation, then a warm reminder before it starts — sent in the founder's own name.
Acceptance
  • Confirmation fires on Mark paid (shared engine — Workshop's AC-WSBK-02 ↗); reminder fires at both offsets (Workshop's AC-WSBK-03 ↗).
Design notes
  • Read-only previews, never editable — bodies are the approved event-templates.ts copy with example vars resolved; nothing invented.
  • Both deliberately link-free UTILITY templates (Peyton 2026-07-10); the reminder is time/location-agnostic, so ONE preview honestly covers both sends.
  • P3 revised (#3789): the confirmation template fires on Mark paid only (paid card). A free-RSVP booking confirms in-window with the session message instead (sec-9 · Mode B bubble 4) — this locked body never sends on the free path.
C2 · “When I need to step in” — the 05 category at full size not built ↑ top in the flow ↑
05 · When I need to step in proposed
If the assistant can't answer
Let On-Brand Support try first
Uses your full knowledge base. Costs one extra AI run per question — only when it's actually used.
Come straight to me
No second attempt, no extra cost.

Only shown when you have On-Brand Support running on this number.

What guests hear while they wait — editable
I'm connecting you with Priya — she'll come back to you shortly.
Where you get pinged
WhatsApp — this thread
Always on. Reply from your phone as usual.
Connected ⌄
Slack
A copy in your channel. On the app? You reply in WhatsApp. API-only number? Reply right in the thread — it reaches your guest.
Connect
C2 · Step-in category seated: Personalize 05 (sec-3/3b) · why: sec-12
  • Escalation radio pair renders only with On-Brand Support on the number (conditional block in the flow frames); holding line + ping rows always render.
Part III — Future design deferred — never a build target. Everything below is drawn so the shape can be judged, NOT so it can be built from. Nothing here is wired today; each item names what actually blocks it.
F1 · Mode E — the event picker (formerly 10) not built ↑ top

The guest hasn't tapped a link and hasn't said "book" — they just message the business, and want to choose between what's on offer. More than half of this already works: the assistant reads booking intent from plain English today (sec-9, Mode A design notes), and since #3789 a founder running 2+ booking cards on one number gets the deterministic name+date list — shipped, drawn as sec-9 · Mode D. What is still missing is the choice within one assistant: a single Event Booking card knows about exactly one event, so it cannot offer its own list.

Mode EGuest just DMs — assistant offers what's open
any payment_mode booking_surface = in_chat NOT BUILT — 2 blockers
What it would take — two named blockers
✓
Reading intent without a keyword ai-composed already works
The assistant classifies the message's intent, so "do you have events coming up?" already lands in the booking flow. The one caveat: on multi-assistant numbers, the keyword router can send a keyword-free message to On-Brand Support instead, which can answer but cannot book — though a catalog-style ask ("what's on?") now gets the deterministic booking list when 2+ booking cards share the number (#3789, Mode D).
1
Knowing what's open ai-composed blocker 1
A live "what's coming up" block — real dates, prices, remaining spots — is built and shipping for On-Brand Support, but the booking assistant never receives it.
2
One event per assistant structural blocker 2
An activated Event Booking assistant is bound to a single event — one date, one price, one spot count, seeded at activation. A picker needs one assistant that holds several events, which a single-event assistant can't do.
·
The picker widget itself native list
Not a blocker. Up to three options use WhatsApp's reply buttons, already built; more than three needs an interactive list message.
Direction — the picker is On-Brand Support's job, not this card's
  • When a founder runs several events, On-Brand Support — the assistant that holds the whole business — answers "what do you run?", presents the list, and routes the guest into the right booking flow. Event Booking stays one event, booked well.
  • The multi-card slice of this shipped without waiting for the picker (#3789): when several booking cards share the number, the engine itself answers a what's-on ask (or an ambiguous "book") with the deterministic name+date list — sec-9 · Mode D. That covers "two events = two activated assistants" today; this F1 frame remains only the one-assistant-many-events case.
  • "Two events = two activated assistants" (sec-3) stays true and untouched — each event keeps its own assistant, date, spot count, and billing. On-Brand Support already holds the business knowledge and the live dates-and-spots block that Event Booking is missing in blocker 1.
  • This makes On-Brand Support a recommended companion for any founder running more than one offering, and a real second charge (see sec-12).
  • Still worth doing on Event Booking regardless: letting it see its own live spot count, so "is Tuesday full?" is answered from data instead of guessed.
F2 · When the guest goes quiet (formerly 11) not built ↑ top

Today, none of this exists. Every mode above ends its automatic behaviour the moment the assistant sends the form or the payment link. If the guest reads it and does nothing, nothing happens — no nudge to them, no signal to the founder, ever. The three toggles that sound like they'd cover this ("notify me on unpaid timeout" and friends) are dead controls with no runtime reader, which is why this rescope removed them from Personalize rather than leave them lying.

The constraint that shapes every option below Meta policy, not a preference
  • Inside 24 hours of the guest's last message, the assistant can write a nudge freely, in the founder's own voice.
  • After 24 hours, only a pre-approved template can be sent — which means every distinct nudge below is a separate submission to Meta, with its own review wait and its own chance of rejection (sec-6b). Each nudge has a real cost, so the set should be small.
  • There is a working precedent to copy rather than invent: On-Brand Support already sends exactly one gentle nudge to a guest who goes quiet for 24h, capped at one per quiet stretch, and backs off automatically when the founder's number is near its sending limit.
CasesFour silences, and what each one deserves
NOT BUILT — proposed shape only
F1
Form sent, opened, never submitted Mode A · B
Today: nothing. Proposed — to the guest, once, at 24h: "Hi Maya, it's Priya — your spot for Tuesday's event isn't saved yet. The form's still open if you'd like it." To the founder: nothing individually — this is normal browsing, not a problem. Roll it into Pulse as a count, not an alert.
F2
Booked, payment link sent, never paid Mode A
Today: nothing — the spot sits held forever and the founder is never told; the chase machinery exists but is switched off for full-payment events by design. Proposed — to the guest, once, at 24h: "Hi Maya, it's Priya — your spot for Tuesday's event is still held. Here's the link again if you'd like to lock it in: …" To the founder: a Pulse attention row — a held spot is a spot not sold, and the founder is the only one who can release it.
—
No “own-form link sent, nothing came back” case on this card
That silence is specific to Workshop Booking's own_form path (a founder's own booking form MystFlo can't see into). v1 is in_chat only, so every capture MystFlo makes is one MystFlo can also see the outcome of.
F4
Waitlisted, a spot opens all modes
Today: the guest is told they're on the waitlist, and then nothing ever happens — there is no sweep that notices a cancellation and tells them. The assistant makes a promise it cannot keep ("you'll be the first to know if a spot opens").
Where this lands in Personalize
  • One new category — "06 · Chasing" — with two toggles: nudge unpaid bookings (on by default), nudge unfinished forms (off by default), each with editable copy. This replaces the three dead "notify me" toggles removed elsewhere in this rescope.
  • No own-form carve-out needed on this card — every capture is in_chat, so the assistant always knows paid from unpaid.
Design notes
  • The smallest honest version is F2 alone — one nudge, one template, one Pulse row, aimed at the only silence that costs the founder money. F1 is polish, F3 is a reporting line rather than a message, and F4 is a bug fix; they don't need to ship together.
  • Every nudge is one per quiet stretch, capped, never stacked with a reminder on the same day — matching the precedent On-Brand Support already runs.
F3 · The waitlist — what we actually capture (formerly 14) not built ↑ top

Whether the guest should see a WhatsApp Flow waitlist form depends on which of three sold-out paths they hit — they behave nothing alike, and only one of them is missing anything a form could fix. Paths 1 and 3 below are covered in sec-13 and are fixed in code, not redesigned here — this section is scoped to path 2 only.

1
Sold out at submit — Mode A/B form fix in code, not a form
The guest already filled the booking form — name, email, party size — before the spot check ran. A waitlist row IS written, but with email: null, discarding data validated two lines earlier (sec-13). No second form belongs here. Asking someone to fill another form immediately after “sorry, sold out” is friction with nothing to show for it — the fix is the one-line email bug, not new UI.
2
Sold out at send — guest just DMs, no form ever sent this is where a form earns its place
The event is already full when the guest asks, so no form is ever sent. A waitlist row is written — but it carries only the phone number, the WhatsApp display name if one is exposed, and email: null, because nothing was ever collected. This is the only path with no name and no email to lose — they were never asked for. That's what a form would add here, and why it belongs on this path and no other. Proposed form below.
3
Typed capture — every instance until Meta approves its Flow overbooks, doesn't waitlist
No spot check exists on this path at all (sec-13 — “the spot cap only exists on ONE path”) — sold-out is never detected, so it overbooks instead of waitlisting. A form is irrelevant here until the spot check ships; that's the real fix, and it's already in the parallel lane.
ProposedA native waitlist form — path 2 only
NOT BUILT same Flow mechanism as booking · new template/Flow

Same shape as the booking Flow (sec-9, Mode A) — a native in-chat form instead of typed capture — fired the moment the sold-out reply goes out, so joining the waitlist costs the guest one tap, not a typed back-and-forth.

Where each message comes from
1
Guest asks guest
Same intent classification as every other mode (sec-9) — no keyword needed.
2
Sold-out reply ai-composed
Already shipped, hard-coded, verbatim (sec-9, mode-q row E). Not editable today — same as now.
◇
Waitlist form native form proposed
A second Flow, published the same way as the booking form (Event's own Flow provisioning, sec-9) — not a reuse of the booking Flow, since it asks for less and fires on a different trigger. Two fields only: name + email.
3
Guest submits guest
Writes the waitlist row with a real name and email this time — the same row that exists today, minus the email: null bug (sec-13).
4
Join confirmation ai-composed proposed
Written live, inside the 24h window the guest just opened by messaging — no template needed for this turn.
Story
  • As a guest who DMs about a sold-out event, I get told I'm on the waitlist and I'm asked, in the same breath, for my name and email — one tap, not a typed back-and-forth.
Design call — why no party size
  • Left out, deliberately. The booking form (sec-9) asks for party size because it's committing a spot right now. A waitlist join isn't committing anything — the number that matters is whatever's true when a spot actually opens, which could be weeks later, so a party size collected today is stale by then. When the founder promotes someone off the waitlist she's messaging them directly anyway (sec-13's attendee-list proposal already shows waitlisted rows for exactly this) — that's the moment to ask, not now. Two fields, not three.
The honest limit
  • Better data collected here does not mean anyone gets told. Without the notify-sweep proposed as F4 in sec-11, a cleanly-filled waitlist is still a list nobody is ever messaged from. This form is the prerequisite for F4, not a substitute for it — email is the one channel that isn't rate-limited by WhatsApp's 24-hour window, so capturing it is what makes F4 reachable for this path.
Where it lands in Personalize
  • No new Personalize control. The sold-out reply is already not editable today; this form is the same class of message — structural, fired automatically, nothing for the founder to configure.
The cost
  • A second per-founder Flow means a second Meta review-and-publish cycle on top of the booking Flow's, with the same fallback-to-typed-capture safety net and the same named recovery path if Meta says no (sec-6b).
F4 · Stripe — auto-mark paid when their payment lands (formerly 15) not built ↑ top

The Connect tab (sec-4) duplicated with ONE tweak: a deferred Stripe row. CPO-ruled post-beta (#3152, ADR-019's phase-2 door) — per canon U9 it must NOT appear in the Active Connect frame, so it lives only here.

Overview Personalize Connect Try
01 · Connect apps
Communication WhatsApp Business
Which number should this assistant answer on?
+1 555-0142 ▾
Payment Stripe
Auto-mark paid when their payment lands. Nothing confirms until payment is real — MystFlo never touches the money.
Future · #3152
AI Model MystFlo AI
Connected by default. Choosing your own AI model is coming later.
Connected ⌄
Storage MystFlo Storage
Your Attendees list on Pulse — every booking lands there, zero clicks.
Connected

No own-form/Google-Sheets conditional row on this card (v1 has no own_form — that's Workshop Booking's frame). Wallet · Brain Check · template notice unchanged from sec-4 — omitted here, this frame exists for the one new Stripe row.

Frame F4 · Stripe auto-settle [Future]
  • Story — As a founder whose guests pay through my Stripe link, a landed payment marks the booking paid on its own — I stop being the human webhook.
Design notes
  • ADR-019 phase-2 door: a Stripe webhook becomes another door into the same shared learnPaid settle path (host Mark-paid on this card; other booking cards also have a roster tick), all metering identically — never a second billing path. Deferred post-beta by CPO ruling; tracked as #3152.
  • Copy is locked ("Nothing confirms until payment is real — MystFlo never touches the money") — the row detects the founder's own Stripe payment landing; MystFlo still processes no money (ADR-019's payment-relay decision is unchanged).
  • Canon U9: this row never appears in the Active Connect frame (sec-4) until #3152 ships — this duplicated frame is the only place it's drawn.

The recurring-schedule question (sec-3) is resolved, not deferred — single-session ships now; the lightweight repeating-session increment lives in #2798, tracked separately.