toga-ai 1.0.286 → 1.0.288
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/2.0/apps/ai-bdr/INDEX.md +2 -0
- package/knowledge/2.0/apps/ai-bdr/architecture.md +20 -2
- package/knowledge/2.0/apps/ai-bdr/features/bdr-web-funnel-plan.md +533 -0
- package/knowledge/2.0/apps/ai-bdr/features/web-funnel-content-model.md +148 -0
- package/knowledge/INDEX.md +2 -1
- package/knowledge/registry.json +9 -0
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [AI-BDR Architecture](architecture.md) | **AI-BDR** is TOGA's outbound AI sales-development representative. | ai-bdr/README.md, ai-bdr/requirements.txt, ai-bdr/.env.example, ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/vapi/templates/assistant.template.json, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py |
|
|
6
|
+
| [BDR Web Funnel — Full Implementation Plan](features/bdr-web-funnel-plan.md) | > **Status:** First iteration (for Alex's review). | bdr/PLAN.md, bdr/mockup/app.jsx, bdr/mockup/screens.jsx, bdr/mockup/components.jsx |
|
|
6
7
|
| [Call Orchestration — PHP Worker ↔ Vapi (the integration seam)](features/call-orchestration.md) | The **PHP worker** is the orchestrator; **Vapi** is the actor. | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/scripts/update_structured_output.py |
|
|
7
8
|
| [Vapi Integration — Assistants, Tools, Structured Output](features/vapi-integration.md) | Everything inside Vapi: the three assistants, the three shared tools, the shared structured-output schema, the Liquid-templated system prompt, and the Python sc | ai-bdr/vapi/templates/assistant.template.json, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md |
|
|
9
|
+
| [Web Funnel — Next.js UI + Config-Driven Campaign Content](features/web-funnel-content-model.md) | The **public-facing web funnel** is the new UI / lead-capture front door of the **same AI-BDR product** documented in `../architecture.md`. | bdr/src/content/schema.ts, bdr/src/content/hubspotProvider.ts, bdr/src/content/useCampaign.ts, bdr/src/content/default.ts, bdr/src/server/leadSink.ts, bdr/src/server/callbackService.ts, bdr/src/app/api/call-now/route.ts, bdr/src/app/api/call-later/route.ts |
|
|
8
10
|
| [New-Campaign Onboarding (6-phase runbook)](workflows/new-campaign-onboarding.md) | The end-to-end procedure for launching a new outbound BDR campaign (industry, offer, target persona, geography). | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/calcom-new-campaign-event-guide.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py |
|
|
@@ -6,8 +6,8 @@ project: AI-BDR
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [akhokhani]
|
|
9
|
+
updated: 2026-07-08
|
|
10
|
+
owners: [akhokhani, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- ai-bdr/README.md
|
|
13
13
|
- ai-bdr/requirements.txt
|
|
@@ -30,6 +30,7 @@ files:
|
|
|
30
30
|
related:
|
|
31
31
|
- features/vapi-integration.md
|
|
32
32
|
- features/call-orchestration.md
|
|
33
|
+
- features/web-funnel-content-model.md
|
|
33
34
|
- workflows/new-campaign-onboarding.md
|
|
34
35
|
---
|
|
35
36
|
|
|
@@ -175,6 +176,19 @@ authored here, then pushed to Vapi by `scripts/update_system_prompt.py`.
|
|
|
175
176
|
- **No Langfuse / OTEL** — there is no TOGA-side LLM call to trace; the
|
|
176
177
|
LLM call happens inside Vapi.
|
|
177
178
|
|
|
179
|
+
## Public web funnel (UI / lead-capture front door)
|
|
180
|
+
|
|
181
|
+
The AI-BDR product now has a **public-facing web funnel** — a **Next.js (App Router)**
|
|
182
|
+
app in its own code repo, **`bdr`** — as its UI and lead-capture front door. It **reuses
|
|
183
|
+
this system's existing calls** (HubSpot contact fetch, Toga upsert + campaign link,
|
|
184
|
+
`requestCall` callback — see *End-to-end call flow* and `features/call-orchestration.md`),
|
|
185
|
+
ported from the external `info` repo behind `LeadSink` / `CallbackService` adapters so all
|
|
186
|
+
secret-bearing calls stay server-side. On top of that reuse it adds a **HubSpot-owned,
|
|
187
|
+
config-driven campaign content layer**: marketing authors campaign content in HubSpot,
|
|
188
|
+
fetched by slug at runtime and mapped to a serializable `CampaignBundle` (config-driven
|
|
189
|
+
components, `DEFAULT` fallback — no `if(campaign===x)`; a copy change ships with no
|
|
190
|
+
redeploy). Full detail: **`features/web-funnel-content-model.md`**.
|
|
191
|
+
|
|
178
192
|
## Key decisions
|
|
179
193
|
|
|
180
194
|
- **Single shared structured-output schema** across all assistants — keeps
|
|
@@ -195,3 +209,7 @@ authored here, then pushed to Vapi by `scripts/update_system_prompt.py`.
|
|
|
195
209
|
|
|
196
210
|
## Change history
|
|
197
211
|
- 2026-06-16 — Initial architecture doc for AI-BDR. (akhokhani)
|
|
212
|
+
- 2026-07-08 — Additive: recorded the public Next.js web funnel (code repo `bdr`) as the
|
|
213
|
+
product's UI / lead-capture front door — reuses this system's existing HubSpot/Toga/callback
|
|
214
|
+
calls and adds a HubSpot-owned, config-driven campaign content layer. See
|
|
215
|
+
`features/web-funnel-content-model.md`. (tcox)
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "BDR Web Funnel — Full Implementation Plan"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: ai-bdr
|
|
5
|
+
project: "AI-BDR"
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: draft
|
|
9
|
+
updated: 2026-07-08
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- bdr/PLAN.md
|
|
13
|
+
- bdr/mockup/app.jsx
|
|
14
|
+
- bdr/mockup/screens.jsx
|
|
15
|
+
- bdr/mockup/components.jsx
|
|
16
|
+
related:
|
|
17
|
+
- architecture.md
|
|
18
|
+
- web-funnel-content-model.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# BDR: Multi-Campaign "AI BDR / Agent Studio" Web App — Implementation Plan
|
|
22
|
+
|
|
23
|
+
> **Status:** First iteration (for Alex's review). Draft, pending sign-off on the open questions
|
|
24
|
+
> in §9 and final campaign direction / verbiage from leadership.
|
|
25
|
+
> **Author:** tcox · **Stack:** TOGA 2.0 front-end · Next.js (App Router) · **Client scope:** shared / internal (TOGA)
|
|
26
|
+
> **Repo (new):** `bdr` · **Two source inputs:** the **mockup** (real UI) + **`info`** (backend to reuse).
|
|
27
|
+
>
|
|
28
|
+
> Product-copy rules the built site must follow (from the mockup's `CLAUDE.md`): **no em dashes** in any
|
|
29
|
+
> shipped string; **one accent source** drives every accent. (This internal planning doc itself uses em
|
|
30
|
+
> dashes for readability; the rule governs what the site ships, not this file.)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 0. Provenance & required inputs
|
|
35
|
+
|
|
36
|
+
This plan comes from the **AI BDR Development Handoff** meeting plus a full read of two codebases.
|
|
37
|
+
`bdr` is the new public **UI / lead-capture front door of TOGA's existing AI-BDR product** (that
|
|
38
|
+
product's backend is documented at `2.0/apps/ai-bdr/`) — only the UI is new. It is an **entirely new
|
|
39
|
+
code repo**, built by combining:
|
|
40
|
+
|
|
41
|
+
1. **The mockup (the real target UI)** in the `bdr` repo's `mockup/` folder — a finished, high-fidelity
|
|
42
|
+
prototype of an "AI BDR / Agent Studio" single-page flow. This is the pixel-perfect blueprint.
|
|
43
|
+
- Source of truth: `app.jsx` (shell/routing/state), `screens.jsx` (the screens), `components.jsx`
|
|
44
|
+
(shared component + data library), `styles.css` (~97 KB of styling), `CLAUDE.md` (copy rules).
|
|
45
|
+
- Prototype tech: React 18 via UMD + in-browser Babel (no build step). Assets under
|
|
46
|
+
`media/ uploads/ agents/ voices/ screens/`. There is also a 14.5 MB single-file
|
|
47
|
+
`AI BDR (standalone).html` export (do not hand-edit; it is a build artifact).
|
|
48
|
+
2. **`info`** — the external `info` repo (github.com/agilantsolutions/info) — a Next.js app whose `/bdr`
|
|
49
|
+
route already implements the **production backend calls we will reuse**: HubSpot contact fetch, Toga
|
|
50
|
+
contact upsert + campaign linking, and callback (call-now / call-later) requests. (BDR is **also
|
|
51
|
+
Next.js**, so we reuse `info`'s framework patterns and its server-side call logic directly; see §5.6.)
|
|
52
|
+
This is where the real integrations live; its marketing UI is a *different, older* funnel we are
|
|
53
|
+
**not** copying.
|
|
54
|
+
|
|
55
|
+
**Attach when handing this plan to Claude for execution:** this file, the meeting transcript, the
|
|
56
|
+
mockup folder, and the `info` repo.
|
|
57
|
+
|
|
58
|
+
**Still pending (does not block the build):** final campaign direction and copy from leadership.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 1. Goal & done-condition
|
|
63
|
+
|
|
64
|
+
**Goal:** Rebuild the mockup's Agent Studio flow as a production **Next.js app** (App Router +
|
|
65
|
+
TypeScript, matching `info`). Next.js is the chosen framework because the site is **highly public**,
|
|
66
|
+
so server-side rendering + SEO matter, and its server (server components + route handlers) is where
|
|
67
|
+
the secret-bearing HubSpot/Toga/content calls run natively (§5.6). Every campaign is defined by
|
|
68
|
+
content authored in HubSpot, so a new campaign launches with **no code changes**, multiple campaigns
|
|
69
|
+
run side by side, and the real HubSpot / Toga / callback backend from `info` is reused directly.
|
|
70
|
+
|
|
71
|
+
**Done when:**
|
|
72
|
+
1. The full flow (Welcome, Build, Capabilities, Call Now, Schedule, Success) renders in Next.js
|
|
73
|
+
with visual parity to the mockup (pixel-perfect pass against the mockup screens).
|
|
74
|
+
2. Two distinct campaigns (for example a generic **BDR-as-a-service** and a **healthcare** variant),
|
|
75
|
+
**each authored in HubSpot**, render differing only in data (copy, Q&A proof points, agent identity,
|
|
76
|
+
accent), with zero component-code differences. Adding a campaign requires **only HubSpot edits** (no
|
|
77
|
+
code, no redeploy).
|
|
78
|
+
3. No user-facing string is hardcoded in a component (grep-verified); all copy comes from the HubSpot
|
|
79
|
+
campaign content, fetched by slug at runtime and resolved through one hook.
|
|
80
|
+
4. The simulated call/schedule actions are wired to the **real backend** (Toga `requestCall`,
|
|
81
|
+
HubSpot/Toga contact upsert) behind a `CallbackService` / `LeadSink` adapter ported from `info`.
|
|
82
|
+
5. Campaign content is fetched from **HubSpot at runtime** via the backend, behind the
|
|
83
|
+
`CampaignContentProvider` seam (§5.2), so marketing edits copy without a developer or a deploy.
|
|
84
|
+
6. Copy honors the mockup rules: no em dashes; a single `--accent` source drives all accents.
|
|
85
|
+
7. No PII in logs; the `info` debug logging is not carried over.
|
|
86
|
+
8. Deploys (AWS Amplify) with documented env vars.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 2. What the mockup actually is (the real UI to rebuild)
|
|
91
|
+
|
|
92
|
+
### 2.1 Screen / state map
|
|
93
|
+
The shell (`app.jsx`) is a screen router with a 3-step rail (Create · Abilities · Connect) plus
|
|
94
|
+
modals. Default flow in **bold**; parked screens are reachable only via hidden dev triggers today.
|
|
95
|
+
|
|
96
|
+
| Screen (`screen` key) | Step | Role | Notes |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `landing` | (rail hidden) | Service selection ("AI BDR" featured + "Talos" coming soon) | Parked; reached via a hidden corner door. |
|
|
99
|
+
| `creator` | 0 Create | Manual agent builder (form tabs, 9-slot grid, live preview, name, color, voice) | **Parked / dev-only** ("Build it myself" is "coming soon"). |
|
|
100
|
+
| **`building`** | 0 Create | **AutoBuild**: theatrical assembly, always lands on Alex / Human / Cobalt | ~5.5 s, then advances to capabilities. |
|
|
101
|
+
| **`capabilities`** | 1 Abilities | **"Meet your agent"**: avatar, 3 skill cards, Q&A pill bank, CTAs | The content-heavy screen. |
|
|
102
|
+
| **`call`** | 2 Connect | **Call Now**: agent recap + phone entry | Submits phone. |
|
|
103
|
+
| **`schedule`** | 2 Connect | **Schedule**: calendar + 30-min slots + phone | 24/7, 12-month window. |
|
|
104
|
+
| **`calling`** | 2 Connect | **Success (calling)**: connecting → connected → ended + call summary + share | Currently a timed simulation. |
|
|
105
|
+
| **`scheduled`** | 2 Connect | **Success (scheduled)**: confirmation | Calendar-invite copy. |
|
|
106
|
+
|
|
107
|
+
**Modals (`app.jsx`):** `Welcome` (first load: "Meet the rep who never sleeps.", primary
|
|
108
|
+
"Build my agent for me"), `WelcomeStep2` (how-it-works, 3 tiles, shown once on first reaching
|
|
109
|
+
capabilities), `EarlyAccess` ("Build it myself" / customization, coming-soon, with walkthrough video).
|
|
110
|
+
|
|
111
|
+
**Default happy path:** `Welcome → (Build my agent) → building → capabilities → call | schedule → calling | scheduled`.
|
|
112
|
+
|
|
113
|
+
### 2.2 Agent identity model (core state)
|
|
114
|
+
`state = { form, sel, color, voice, voiceGender, name }`:
|
|
115
|
+
- **form**: `Human | Owl | Robot`; **sel**: chosen slot index per form (0..8, 9 slots each).
|
|
116
|
+
- **color**: one of 5 TOGA accents (`#FF6AB0` Fuchsia, `#CF9AFF` Lilac, `#4571DD` Cobalt,
|
|
117
|
+
`#FFA071` Peach, `#A2AAE1` Indigo); drives the global `--accent` (+ derived `--accent-fill`).
|
|
118
|
+
- **voice**: `voiceGender` (Female/Male) + index into `VOICE_GROUPS`.
|
|
119
|
+
- **name**: free text, falls back to a per-form sample name (`AGENT_NAMES`, for example Human ->
|
|
120
|
+
Maya/Alex/Zoe...). Default auto-build result is **Alex** (Human slot 1, Cobalt, Female voice 0).
|
|
121
|
+
- Persisted to `localStorage` (`aibdr.v2`); only voice survives a refresh, flow restarts at Welcome.
|
|
122
|
+
|
|
123
|
+
> Design implication: in the default flow the user does **not** pick the agent (the builder is
|
|
124
|
+
> parked); AutoBuild fixes it to Alex/Cobalt. So **agent identity is effectively campaign config**
|
|
125
|
+
> today (which art, accent, voice, name a campaign presents). See §6.
|
|
126
|
+
|
|
127
|
+
### 2.3 Shared component + data library (`components.jsx`)
|
|
128
|
+
Reusable pieces to port (heavy reuse is exactly why "build one screen cleanly, the rest are fast"):
|
|
129
|
+
- **Structural/UI:** `Wordmark`, `StepRail`, `BackBtn`, `SynapseButton`, `CtlHead`, `IqMark`, `Ico`
|
|
130
|
+
(full inline SVG icon set), `Silhouette`.
|
|
131
|
+
- **Agent visuals:** `AgentArt` / `SmoothImg` / `agentArt` + `AGENT_ART`, `TalkingPreview` +
|
|
132
|
+
`AGENT_TALK` (per-form talking video), `AgentSlot`, `ColorSelector` + `AGENT_COLORS`,
|
|
133
|
+
`VoiceDial` + `VOICE_GROUPS` + `VoiceAudio` + `voiceName`, `AGENT_NAMES` + `sampleName`.
|
|
134
|
+
- **Motion/system:** `SwapFade` (the one page/tab transition), `Particles` (ambient motes),
|
|
135
|
+
plus app-level cursor-hover tracking and scroll-state tinting (in `app.jsx`).
|
|
136
|
+
- **Asset resolution:** `ASSET(path)` maps a logical path to `window.__resources` (embedded assets
|
|
137
|
+
in the standalone export) or to a real URL. `preloadAgentAssets(form, sel)` warms art + video.
|
|
138
|
+
- **Dev tuning:** `TweaksPanel` / `TweakSection` / `TweakColor` / `TweakSlider` (synapse glow,
|
|
139
|
+
radius). Likely dev-only; decide whether to keep in production (§9).
|
|
140
|
+
|
|
141
|
+
### 2.4 Content inventory (everything that must become campaign config)
|
|
142
|
+
This is the "verbiage" the meeting wants swappable. Current hardcoded copy lives in these surfaces:
|
|
143
|
+
- **Welcome / WelcomeStep2 / EarlyAccess** (headlines, value bullets, how-it-works tiles, CTA labels).
|
|
144
|
+
- **Landing** (headline, lede, the two service tiles).
|
|
145
|
+
- **Capabilities**: `CAPS` (3 skill cards), `Q_ANCHOR` + `Q_POOL` (anchor + a 10-entry Q&A pool, 11
|
|
146
|
+
total) and `Q_SETS` (rotation A/B/C), lede, CTA labels, "avg connect time" line.
|
|
147
|
+
- **Call Now / Schedule**: prompts, phone-field labels, privacy footnotes, durations.
|
|
148
|
+
- **Success**: `CALL_SUMMARIES` (short/medium/large recaps), status strings, calendar-invite copy,
|
|
149
|
+
`AmbientQuestions` (reuses the Q pool).
|
|
150
|
+
- **AutoBuild**: build-step labels ("Selecting your rep / Tuning the voice / Setting the style").
|
|
151
|
+
|
|
152
|
+
> Note: the `Q_POOL` answers already contain **healthcare/pharma proof points** ("cut inventory
|
|
153
|
+
> costs 32%", "recovered $167K in 90 days", "response times dropped 44%") alongside generic BDR
|
|
154
|
+
> answers. This is direct evidence that campaign copy (BDR-as-a-service vs. healthcare) must be
|
|
155
|
+
> bundle-swappable, and gives us real content for the two example bundles in the done-condition.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 3. What `info` provides (backend to reuse, not its UI)
|
|
160
|
+
**Many of the API calls the new BDR needs already exist and work in `info` — reuse them, do not
|
|
161
|
+
rebuild.** The `info/bdr` route is a live implementation of the lead + callback backend; port these
|
|
162
|
+
modules into `bdr` (behind the §5.6 adapters, minus the debug logging) rather than writing them from
|
|
163
|
+
scratch. From the external `info` repo's `src` folder:
|
|
164
|
+
- **HubSpot** (`lib/hubspot.ts`): `getContact(hsContactId)`, env `HUBSPOT_ACCESS_TOKEN`.
|
|
165
|
+
- **Toga / api2** (`lib/toga.ts`, envelope `{isSuccess,status,error,messages,data}`):
|
|
166
|
+
cached-token auth; `upsertContactByEmail` (find/create/update) + `addContactToCampaign`;
|
|
167
|
+
`requestCall(uuid, dtNextContactRequested, phone, IMMEDIATE|SCHEDULED)` which drives the actual
|
|
168
|
+
voice callback; `CONTACT_CALL_TYPE_UUID` immediate/scheduled; CST datetime formatting.
|
|
169
|
+
Env: `TOGA_API_BASE_URL`, `TOGA_CLIENT_ID`, `TOGA_CLIENT_API_UUID`, `TOGA_CLIENT_API_SECRET`.
|
|
170
|
+
- **Entry params**: `?hsContactId=…&hsCampaignId=…` (a server component prefetches the contact +
|
|
171
|
+
upserts on request; BDR keeps this same Next.js server-component pattern).
|
|
172
|
+
- **API routes**: `call-now`, `call-later`, `contact` (and a `visit` 501 stub).
|
|
173
|
+
- **GA4** (`lib/analytics.ts`): typed `EventMap`, env `NEXT_PUBLIC_GA_MEASUREMENT_ID`.
|
|
174
|
+
- **Do not port**: the extensive debug `console.log`s (several log PII: email/phone), and `info`'s
|
|
175
|
+
hero/curiosity marketing UI.
|
|
176
|
+
|
|
177
|
+
**Reconciliation:** the mockup's Call Now / Schedule screens are the front-end; `info`'s
|
|
178
|
+
`requestCall` / upsert are the backend those screens should call (replacing the mockup's timed
|
|
179
|
+
simulation in `Success`). The mockup already models immediate vs. scheduled, matching
|
|
180
|
+
`CONTACT_CALL_TYPE_UUID`.
|
|
181
|
+
|
|
182
|
+
**How `info` uses "campaign" (verified by reading the source):** it is **attribution-only**. HubSpot is
|
|
183
|
+
used solely to fetch the *contact* (`getContact(hsContactId)`); the `hsCampaignId` from the URL is
|
|
184
|
+
passed to **Toga** as a campaign UUID (`addContactToCampaign` → `POST /campaigns-contacts`), linking
|
|
185
|
+
the contact to a campaign on the Toga/api2 side. **`info` does not fetch any marketing content from
|
|
186
|
+
HubSpot** — its copy is hardcoded. So we reuse `info`'s contact fetch + Toga campaign-link + callback,
|
|
187
|
+
but **fetching campaign *content* by slug from HubSpot is net-new** (no `info` pattern to copy; see
|
|
188
|
+
§9.10). Note the slug (content key) and the campaign UUID (attribution key) are related but distinct;
|
|
189
|
+
the bundle carries the UUID so a slug maps to the right campaign for logging.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 4. Decisions locked at kickoff
|
|
194
|
+
| Decision | Choice | Consequence |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| Repo name | **`bdr`** | New greenfield repo. Not yet in the TOGA registry. |
|
|
197
|
+
| Framework | **Next.js (App Router) + TypeScript** | Chosen because the site is highly public: SSR + SEO, and a built-in server for the secret-bearing HubSpot/Toga/content calls. Matches `info`, so its patterns port directly. |
|
|
198
|
+
| UI source | **The mockup** | Rebuild its Agent Studio flow faithfully (pixel-perfect). The mockup is React, so components port cleanly into Next.js client components. |
|
|
199
|
+
| Backend source | **`info`** | Reuse HubSpot/Toga/callback logic directly (same framework). |
|
|
200
|
+
| Content model | **Runtime, HubSpot-owned** | Campaign content is authored by the **marketing team in HubSpot** and fetched **by slug at runtime** via the backend. **No campaign content in the repo; no developer or redeploy to change copy.** (Supersedes the earlier "static bundles now" idea.) See §5.2 / §5.5. |
|
|
201
|
+
| Backend relationship | **Same product; reuses the AI-BDR backend** | This funnel is the new UI / front door of TOGA's existing **AI-BDR** product; it reuses that system's HubSpot/Toga/callback calls (documented at `2.0/apps/ai-bdr/`). Lead/callback handoff still goes behind `LeadSink`/`CallbackService` adapters for a clean seam. |
|
|
202
|
+
| Campaign entry | **Plan recommends** | See §5.5. |
|
|
203
|
+
| Copy rules | **No em dashes; one accent source** | Accent enforced in code. No-em-dash cannot be linted on HubSpot-authored copy: enforce via author guidance + a normalization pass in the backend mapping. |
|
|
204
|
+
| Client | Shared / internal | No per-client theming/entitlement logic. |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 5. Target architecture
|
|
209
|
+
|
|
210
|
+
### 5.1 Prototype → production porting strategy
|
|
211
|
+
The mockup is already **React** (React 18 via UMD + in-browser Babel, no build step). Production is a
|
|
212
|
+
**Next.js (App Router) + TypeScript** app, matching `info` (note `info/AGENTS.md`: Next has breaking
|
|
213
|
+
changes vs. older versions; read the bundled docs in `node_modules/next/dist/docs/` before coding).
|
|
214
|
+
Port approach:
|
|
215
|
+
- Move `styles.css` in as the base stylesheet (Tailwind optional; the mockup is hand-authored CSS
|
|
216
|
+
with CSS variables, so keep it and layer Tailwind only if desired). Preserve the CSS-variable
|
|
217
|
+
accent system verbatim.
|
|
218
|
+
- Convert each `components.jsx` / `screens.jsx` unit into a real module. The interactive Agent Studio
|
|
219
|
+
screens are **client components** (`"use client"`, they use state/effects/animation); the top-level
|
|
220
|
+
page is a **server component** that resolves the campaign + primes the lead server-side, then hands
|
|
221
|
+
data to the client flow. Replace UMD globals / `window.*` with imports. Keep `SwapFade` / `Particles`.
|
|
222
|
+
- Replace `ASSET`/`window.__resources` with the Next asset pipeline (files in `public/`, imported URLs).
|
|
223
|
+
Migrate agent art / talking videos / walkthrough video / particles.
|
|
224
|
+
- Replace `localStorage` boot logic with the same behavior in a client provider/hook.
|
|
225
|
+
|
|
226
|
+
> **Server = built in (this is why Next.js helps here).** `info`'s HubSpot/Toga calls use **secrets**
|
|
227
|
+
> (`HUBSPOT_ACCESS_TOKEN`, `TOGA_CLIENT_API_SECRET`, ...) that must never ship to the browser. In
|
|
228
|
+
> Next.js those calls live in **server components + route handlers**, so there is no separate backend
|
|
229
|
+
> or service to stand up. Keep secret-bearing code out of client components and never expose it via
|
|
230
|
+
> `NEXT_PUBLIC_` env.
|
|
231
|
+
|
|
232
|
+
### 5.2 Content provider — HubSpot is the source of truth
|
|
233
|
+
**Campaign content lives in HubSpot, authored by marketing, and is fetched by slug at runtime.**
|
|
234
|
+
It is **not** stored in the repo, and changing copy requires **no developer and no redeploy**. The
|
|
235
|
+
provider is the seam that makes this true:
|
|
236
|
+
```ts
|
|
237
|
+
interface CampaignContentProvider {
|
|
238
|
+
resolve(slug: string | undefined): Promise<CampaignBundle>; // returns the safety-net fallback if unknown/unreachable
|
|
239
|
+
list?(): Promise<CampaignSummary[]>;
|
|
240
|
+
}
|
|
241
|
+
// Primary: HubSpotContentProvider — runs server-side (in the page's server component / a route
|
|
242
|
+
// handler). Reads the campaign content from HubSpot (see §9.10 for the exact HubSpot
|
|
243
|
+
// mechanism), maps it to a CampaignBundle, and caches it. Secrets stay server-side.
|
|
244
|
+
// Fallback: a minimal built-in DEFAULT bundle (engineering safety net only, NOT authored marketing
|
|
245
|
+
// copy) so the funnel still renders if a slug is unknown or HubSpot is briefly unreachable.
|
|
246
|
+
```
|
|
247
|
+
`resolve(slug)` runs on the server during render (SSR), so the HubSpot fetch + field mapping to the
|
|
248
|
+
`CampaignBundle` shape (§6) happens before the page is sent, with Next caching / revalidation so
|
|
249
|
+
marketing edits appear quickly without a deploy (and the public site stays SEO-friendly). The client
|
|
250
|
+
components receive the resolved bundle as props / via `useCampaign()`; they never see HubSpot.
|
|
251
|
+
|
|
252
|
+
> **Key sub-decision (open, §9.10):** *how* campaign content is represented in HubSpot. HubSpot's
|
|
253
|
+
> "Campaigns" tool is built for attribution, not rich structured copy, so this depth of content
|
|
254
|
+
> (headlines, a ~10-entry Q&A pool, agent identity, call summaries) needs a deliberate home. Likely
|
|
255
|
+
> **HubDB** (a CMS-Hub table: one row per campaign, a `slug` column + content columns, API-fetchable)
|
|
256
|
+
> or a **custom object**. This choice drives the fetch + the field mapping and needs confirmation with
|
|
257
|
+
> whoever owns the HubSpot instance.
|
|
258
|
+
|
|
259
|
+
### 5.3 Campaign as data, components as renderers
|
|
260
|
+
Follow the TOGA config-driven precedents (`toga25-supply` `useClientFields`; `toga2-commerce` Cart
|
|
261
|
+
C1..C7): resolve a bundle keyed by campaign, always with a `DEFAULT` fallback; render through the
|
|
262
|
+
components; never branch `if (campaign === 'x')` in JSX. Non-serializable behavior (a CTA action,
|
|
263
|
+
an icon) is referenced by an **enum key** hydrated via a `Record<key, fn|Component>` registry at the
|
|
264
|
+
view-model layer, so bundles stay pure data (so HubSpot only ever stores plain strings/values).
|
|
265
|
+
|
|
266
|
+
### 5.4 Flow state machine
|
|
267
|
+
A `useAgentFlow(campaign)` hook owns `screen` + transitions (the `go()` / `back` map from `app.jsx`),
|
|
268
|
+
the modal gates (`welcome`, `welcome2`, `earlyAccess`), and step derivation (`stepFor`). Screens are
|
|
269
|
+
presentational and receive copy from the bundle + handlers from the hook. This mirrors the existing
|
|
270
|
+
shell but makes the copy and agent identity injected rather than inlined.
|
|
271
|
+
|
|
272
|
+
### 5.5 How the front end knows which campaign to display (URL-driven, slug-keyed)
|
|
273
|
+
**Short answer: yes, it is URL-driven. Every campaign has a stable `slug`; the server reads the slug
|
|
274
|
+
from the request URL and loads that campaign's data through the provider (at render time).** End to end:
|
|
275
|
+
|
|
276
|
+
**1. Each campaign has a slug.** A short, stable identifier, for example `bdr-service`, `healthcare`.
|
|
277
|
+
The slug is the key marketing's HubSpot campaign is looked up by. It lives **in HubSpot, not the
|
|
278
|
+
repo** (§5.2); the repo only holds the type/schema, the HubSpot->bundle mapping, and a minimal
|
|
279
|
+
safety-net `DEFAULT`.
|
|
280
|
+
|
|
281
|
+
**2. The URL carries the slug.** Two supported shapes (both resolved by one `resolveCampaignId()`):
|
|
282
|
+
- **Query param (recommended to ship first):** `bdr.togatech.com/?campaign=bdr-service`. Matches how
|
|
283
|
+
leads actually arrive: HubSpot outreach links are query-based, and `info` already reads a
|
|
284
|
+
`?hsCampaignId=` param, so this slots into existing outreach with no new infrastructure.
|
|
285
|
+
- **Path (add later, optional):** `bdr.togatech.com/c/bdr-service`. Cleaner shareable URLs; added via
|
|
286
|
+
the client router without touching components once the query path exists.
|
|
287
|
+
|
|
288
|
+
**3. Resolution order** (first match wins): explicit `?campaign=<slug>` -> mapped `?hsCampaignId=<id>`
|
|
289
|
+
(id -> slug lookup, so CRM links keep working) -> `DEFAULT`. An unknown or missing slug always falls
|
|
290
|
+
back to the `DEFAULT` bundle, so the funnel never fails to render.
|
|
291
|
+
|
|
292
|
+
**4. The slug drives a runtime fetch of the content from HubSpot** (this is the confirmed requirement):
|
|
293
|
+
the page's server component calls `provider.resolve(slug)`, which reads that campaign's content from
|
|
294
|
+
**HubSpot** (§9.10), maps it to a `CampaignBundle`, and caches it (Next revalidation). Because this is
|
|
295
|
+
server-side, the fully-populated page is rendered and sent (good for the public site's SEO). Marketing
|
|
296
|
+
edits in HubSpot show up on the next revalidation. **No repo content, no redeploy, no developer** to
|
|
297
|
+
change copy. If the slug is unknown or HubSpot is briefly unreachable, the minimal `DEFAULT` safety net
|
|
298
|
+
renders so the funnel never breaks.
|
|
299
|
+
|
|
300
|
+
**5. Multiple campaigns run simultaneously by construction.** Selection is per-visitor, derived from
|
|
301
|
+
their URL each load; there is no global "current campaign" state. Any number of campaigns are live at
|
|
302
|
+
once, each just a different slug.
|
|
303
|
+
|
|
304
|
+
**6. CRM/analytics linkage travels in the bundle.** The resolved bundle carries `hsCampaignId` /
|
|
305
|
+
`togaCampaignUuid`, used when the lead is upserted/linked (§5.6) and for per-campaign GA4, so every
|
|
306
|
+
lead and event is attributed to the right campaign.
|
|
307
|
+
|
|
308
|
+
**Recommendation:** ship **query-param entry + the HubSpot content provider** from the start (that is
|
|
309
|
+
the confirmed requirement); add **path-based routing** as a nicety later. Keep all of this behind
|
|
310
|
+
`resolveCampaignId()` + `useCampaign()` so none of it is visible to the screens. The one thing that
|
|
311
|
+
must be pinned down before Phase 1 is **how the content is modeled in HubSpot** (§9.10).
|
|
312
|
+
|
|
313
|
+
### 5.6 Backend adapters (run in the Next.js server)
|
|
314
|
+
The client screens call two interfaces; both run **server-side** (in route handlers / server actions),
|
|
315
|
+
so secrets never reach the browser:
|
|
316
|
+
```ts
|
|
317
|
+
interface LeadSink { upsertLead(input): Promise<LeadRef>; } // today: Toga upsert + HubSpot
|
|
318
|
+
interface CallbackService { requestCall(ref, whenOrNow, phone): Promise<void>; } // today: Toga requestCall
|
|
319
|
+
```
|
|
320
|
+
Because BDR is Next.js, these live in the app's own server layer (route handlers such as
|
|
321
|
+
`app/api/call-now/route.ts`, mirroring `info`), so **there is no separate backend to host**.
|
|
322
|
+
Implementation is `info`'s existing logic (`lib/toga.ts`,
|
|
323
|
+
`lib/hubspot.ts`, and its `call-now` / `call-later` route handlers): it **already exists and works**,
|
|
324
|
+
we port it directly, minus the PII logging. The mockup's `Success` simulation is replaced by a real
|
|
325
|
+
`CallbackService` call (immediate vs. scheduled maps to `CONTACT_CALL_TYPE_UUID`).
|
|
326
|
+
|
|
327
|
+
This funnel is the front door of the existing **AI-BDR** product, so `CallbackService` is implemented
|
|
328
|
+
with that system's callback path (the `info`-ported Toga `requestCall`). The adapter seam keeps the
|
|
329
|
+
client and interfaces stable if that backend path changes.
|
|
330
|
+
|
|
331
|
+
### 5.7 `togatech` website integration (eventual)
|
|
332
|
+
The site will ultimately be surfaced through the TOGA Technology website (`togatech` repo). Design for
|
|
333
|
+
it now: keep the funnel self-contained and embeddable, prefer a linkable per-campaign URL so
|
|
334
|
+
`togatech` CTAs deep-link into a campaign, and express shared branding as tokens. Integration
|
|
335
|
+
mechanism (subdomain / linked Amplify app vs. iframe vs. absorption into `togatech`) is an open
|
|
336
|
+
question (§9), needed before deploy, not before building.
|
|
337
|
+
|
|
338
|
+
### 5.8 Asset pipeline
|
|
339
|
+
Migrate agent art (Human/Owl/Robot × up-to-9 slots), talking videos, the customization walkthrough
|
|
340
|
+
video, and particle/Lottie assets into `public/`. Keep `preloadAgentAssets` behavior so screen swaps
|
|
341
|
+
never blank. Prune unused slots per the campaigns actually shipped (the default flow uses one agent).
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 6. Campaign bundle schema (concrete sketch)
|
|
346
|
+
This is the shape the app consumes. It is **produced server-side by mapping the HubSpot campaign
|
|
347
|
+
content (§5.2) into this structure** — it is the contract between "how marketing's content is stored
|
|
348
|
+
in HubSpot" and "what the components render," and the mapping layer is where HubSpot fields become
|
|
349
|
+
these fields (and where the no-em-dash normalization runs). Serializable data only; behavior/icons by
|
|
350
|
+
key. Covers the real content surfaces from §2.4.
|
|
351
|
+
|
|
352
|
+
> The richer fields here (the nested `pool: QA[]` + `sets`, `callSummaries`, `RichLine` accent spans)
|
|
353
|
+
> are the parts hardest to represent in a marketing-friendly way in HubSpot — a direct input to the
|
|
354
|
+
> §9.10 modeling decision. Flat columns handle the simple strings; nested arrays likely need child
|
|
355
|
+
> rows or a structured (JSON) field.
|
|
356
|
+
```ts
|
|
357
|
+
interface AgentIdentity {
|
|
358
|
+
form: "Human" | "Owl" | "Robot";
|
|
359
|
+
slot: number; // 0..8
|
|
360
|
+
accent: string; // one of the 5 TOGA accents -> --accent
|
|
361
|
+
voiceGender: "Female" | "Male";
|
|
362
|
+
voiceIndex: number;
|
|
363
|
+
name: string; // e.g. "Alex"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
interface QA { id: string; q: [string, string, string]; a: string; } // a: first-person, no em dashes
|
|
367
|
+
|
|
368
|
+
interface CampaignBundle {
|
|
369
|
+
id: string; // slug
|
|
370
|
+
hsCampaignId?: string; // HubSpot linkage
|
|
371
|
+
togaCampaignUuid?: string; // Toga addContactToCampaign linkage
|
|
372
|
+
agent: AgentIdentity; // who the campaign presents (default flow does not let user pick)
|
|
373
|
+
welcome: { eyebrow: string; heading: string; body: string; values: string[]; primaryCta: string; };
|
|
374
|
+
landing?: { heading: RichLine; lede: string; tiles: ServiceTile[]; }; // if the service picker is used
|
|
375
|
+
capabilities: {
|
|
376
|
+
step2Intro: { heading: string; body: string; values: string[]; tiles: HowTile[]; };
|
|
377
|
+
heading: RichLine; lede: string;
|
|
378
|
+
skills: { icon: string; title: string; desc: string }[]; // CAPS
|
|
379
|
+
anchor: QA; pool: QA[]; sets: string[][]; // Q&A bank + rotation
|
|
380
|
+
ctas: { callNow: string; schedule: string; connectNote: string; };
|
|
381
|
+
};
|
|
382
|
+
call: { heading: string; body: string; footnote: string; };
|
|
383
|
+
schedule: { heading: string; footnote: string; durationLabel: string; };
|
|
384
|
+
success: { callSummaries: { key: string[]; next: string[] }[]; scheduledNote: string; };
|
|
385
|
+
build?: { steps: { label: string; value?: string }[]; }; // AutoBuild beats
|
|
386
|
+
analytics?: { measurementId?: string; eventPrefix?: string };
|
|
387
|
+
features?: string[]; // capability flags (e.g. "showServicePicker", "allowManualBuilder")
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
`RichLine` supports the mockup's accent-inked span (for example `"<agent> is ready to work."`).
|
|
391
|
+
The provider must always return a valid bundle: on an unknown slug or a HubSpot hiccup it falls back
|
|
392
|
+
to a minimal built-in `DEFAULT` (a safety net, not authored marketing copy) so the funnel still renders.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 7. Proposed repo structure (Next.js App Router + TypeScript)
|
|
397
|
+
```
|
|
398
|
+
bdr/
|
|
399
|
+
src/
|
|
400
|
+
app/
|
|
401
|
+
layout.tsx globals.css # shell + ported styles.css / CSS variables
|
|
402
|
+
page.tsx # SERVER component: resolve slug -> campaign, prime lead, render flow
|
|
403
|
+
c/[slug]/page.tsx # optional path-based campaign entry (later)
|
|
404
|
+
api/{lead,call-now,call-later}/route.ts # SERVER: secret-bearing Toga/HubSpot calls (ported from info)
|
|
405
|
+
content/
|
|
406
|
+
schema.ts # CampaignBundle types (the contract)
|
|
407
|
+
hubspotProvider.ts # SERVER: reads campaign content from HubSpot + maps to bundle (§5.2/§6)
|
|
408
|
+
useCampaign.ts # client hook to read the resolved bundle
|
|
409
|
+
default.ts # minimal safety-net fallback ONLY (not authored copy)
|
|
410
|
+
# NOTE: no per-campaign content files here — campaign copy lives in HubSpot, not the repo
|
|
411
|
+
flow/
|
|
412
|
+
useAgentFlow.ts # screen state machine + modal gates + step rail (client)
|
|
413
|
+
screens/{Landing,AutoBuild,Capabilities,CallNow,Schedule,Success}.tsx # "use client"
|
|
414
|
+
modals/{Welcome,WelcomeStep2,EarlyAccess}.tsx
|
|
415
|
+
components/ # ported shared lib (SwapFade, Particles, AgentArt, VoiceDial, ColorSelector, SynapseButton, StepRail, Wordmark, Ico, ...)
|
|
416
|
+
server/ # secret-bearing modules, ported from info (PII logging removed)
|
|
417
|
+
toga.ts hubspot.ts leadSink.ts callbackService.ts
|
|
418
|
+
lib/ analytics.ts assetPath.ts formatPhone.ts agentData.ts # AGENT_ART/NAMES/COLORS/VOICES
|
|
419
|
+
public/ # migrated agent art, talking videos, walkthrough, particles
|
|
420
|
+
amplify.yml next.config.ts tsconfig.json eslint (no-em-dash copy rule on in-repo strings)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 8. Phased execution plan
|
|
426
|
+
Sizing note (from the meeting): once one screen is built cleanly the rest go fast (heavy component
|
|
427
|
+
reuse); the long pole is pixel-perfect UI parity, not logic. We hand Claude the actual mockup source,
|
|
428
|
+
so fidelity should be near-exact.
|
|
429
|
+
|
|
430
|
+
- **Phase 0 — Bootstrap `bdr`.** New Next.js (App Router) + TypeScript app (mirror `info`'s config;
|
|
431
|
+
Amplify SSR hosting). Port `styles.css` and the CSS-variable accent system. Set up campaign routing
|
|
432
|
+
(query param now, `/c/[slug]` later) and ESLint incl. a no-em-dash check on in-repo strings. Read
|
|
433
|
+
`node_modules/next/dist/docs/` first (Next breaking changes per `info/AGENTS.md`).
|
|
434
|
+
- **Phase 1 — Content layer.** Depends on the §9.10 HubSpot-modeling decision. Define `schema.ts`
|
|
435
|
+
(the `CampaignBundle` contract), the server-side `hubspotProvider` (`resolve(slug)` reads HubSpot +
|
|
436
|
+
maps to a bundle, with caching/revalidation + no-em-dash normalization), the `useCampaign` client
|
|
437
|
+
hook, and a minimal safety-net `DEFAULT`. Seed HubSpot with one real campaign (extract the mockup's
|
|
438
|
+
current copy into HubSpot, not the repo) to develop against. Unit-test mapping + fallback.
|
|
439
|
+
- **Phase 2 — Port the component library.** Bring `components.jsx` across as real modules (SwapFade,
|
|
440
|
+
Particles, AgentArt + data, VoiceDial, ColorSelector, SynapseButton, StepRail, Wordmark, Ico,
|
|
441
|
+
asset/preload). Migrate assets to `public/`. This unblocks every screen.
|
|
442
|
+
- **Phase 3 — Build one screen end to end (Capabilities), config-driven.** It is the richest
|
|
443
|
+
(skills + Q&A bank + CTAs), so building it first calibrates the real estimate and proves the bundle
|
|
444
|
+
schema before the remaining screens.
|
|
445
|
+
- **Phase 4 — Remaining screens + modals from config.** AutoBuild, Landing (behind a feature flag),
|
|
446
|
+
Call Now, Schedule, Success, Welcome/WelcomeStep2/EarlyAccess, StepRail. Remove all hardcoded copy.
|
|
447
|
+
- **Phase 5 — Wire the real backend.** Port `info`'s `lib/toga.ts` / `lib/hubspot.ts` + its call
|
|
448
|
+
route handlers into BDR's `app/api/*` (server-side); wire the `LeadSink` / `CallbackService`
|
|
449
|
+
adapters; replace the `Success` simulation with real immediate/scheduled `requestCall`; remove PII
|
|
450
|
+
logging; resolve the `visit` route (implement or drop).
|
|
451
|
+
- **Phase 6 — Second campaign + multi-campaign.** Author a second campaign **in HubSpot** (for example
|
|
452
|
+
a `healthcare` variant using the healthcare proof points) alongside `bdr-service`; prove that adding a
|
|
453
|
+
campaign needs only HubSpot edits (no code, no deploy) and zero component diffs; validate slug
|
|
454
|
+
resolution and simultaneous campaigns.
|
|
455
|
+
- **Phase 7 — Analytics + polish.** Per-campaign GA4, pixel-perfect pass vs. the mockup, motion/timing
|
|
456
|
+
parity, accessibility check.
|
|
457
|
+
- **Phase 8 — Deploy.** Amplify env vars, README, "how to add a campaign" doc; confirm `togatech`
|
|
458
|
+
integration mechanism.
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## 9. Open questions (leadership + HubSpot owner). Most do not block early work; **§9.10 gates Phase 1**.
|
|
463
|
+
1. **Backend relationship:** RESOLVED — `bdr` is the new UI / front door of TOGA's existing **AI-BDR**
|
|
464
|
+
product (not standalone); it reuses that system's backend calls, and its knowledge is filed under
|
|
465
|
+
`2.0/apps/ai-bdr/`. `CallbackService` uses that system's callback path.
|
|
466
|
+
2. **Runtime editability:** RESOLVED — content is authored by marketing **in HubSpot** and fetched by
|
|
467
|
+
slug at runtime; no repo content, no redeploy. The remaining question is the HubSpot mechanism (§9.10).
|
|
468
|
+
3. **Agent choice:** Stay with a campaign-fixed agent (auto-build to a preset), or ship the manual
|
|
469
|
+
Creator (currently parked / "coming soon")? Determines whether `Creator` + full slot art ship now.
|
|
470
|
+
4. **Service picker:** Is the `Landing` service-selection screen (AI BDR + "Talos coming soon") in
|
|
471
|
+
scope for launch, or parked like in the mockup?
|
|
472
|
+
5. **Campaign catalog:** How many campaigns at launch and who authors copy? Confirms entry UX (§5.5).
|
|
473
|
+
6. **`togatech` integration mechanism:** subdomain / linked Amplify app vs. iframe/embed vs. absorption
|
|
474
|
+
into `togatech`. Needed before Phase 8.
|
|
475
|
+
7. **Tweaks panel:** keep the dev tuning panel (synapse/radius/accent) in production or strip it?
|
|
476
|
+
8. **First campaign direction + verbiage** (BDR-as-a-service vs. healthcare): needed only to fill the
|
|
477
|
+
first bundle, not to build the engine.
|
|
478
|
+
9. **Backend hosting:** RESOLVED by the Next.js choice — the secret-bearing HubSpot/Toga calls run in
|
|
479
|
+
BDR's own server components / route handlers; no separate backend to host. Only remaining item is
|
|
480
|
+
confirming the Amplify **SSR** hosting target (not static) at deploy.
|
|
481
|
+
10. **HubSpot content mechanism (blocks Phase 1) — the big one.** *How* is campaign content stored in
|
|
482
|
+
HubSpot so it is marketing-editable and fetchable by slug? `info` gives no precedent (it only reads
|
|
483
|
+
contacts). Options: **HubDB** (CMS-Hub table, one row per campaign, `slug` column + content columns,
|
|
484
|
+
API-fetchable — the likely fit) vs. a **custom object** vs. properties on the Campaigns object. This
|
|
485
|
+
determines the fetch + the HubSpot->`CampaignBundle` mapping. Sub-parts to settle with the HubSpot
|
|
486
|
+
owner: which mechanism; how the **nested content** (Q&A pool + rotation sets, call summaries, accent
|
|
487
|
+
`RichLine`s) is represented (flat columns can't hold arrays cleanly — child rows or a JSON field);
|
|
488
|
+
who authors/owns the schema; and how the slug relates to the existing `hsCampaignId`/Toga campaign
|
|
489
|
+
UUID used for attribution.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 10. Risks & gotchas
|
|
494
|
+
- **Next breaking changes** (per `info/AGENTS.md`): read the bundled `node_modules/next/dist/docs/`
|
|
495
|
+
before coding; do not assume older-Next APIs.
|
|
496
|
+
- **Keep secrets in server code.** `HUBSPOT_ACCESS_TOKEN` / `TOGA_CLIENT_API_SECRET` and the
|
|
497
|
+
token-exchange must live in server components / route handlers, never in a `"use client"` component
|
|
498
|
+
or a `NEXT_PUBLIC_` env var (those ship to the browser). The interactive screens are client
|
|
499
|
+
components, so the boundary matters: secret-bearing calls stay in `app/api/*` + server modules.
|
|
500
|
+
- **No `2.0/standards/frontend.md`** exists; `toga2-view` conventions are the de-facto standard
|
|
501
|
+
(single axios/api client, `use*ViewModel` hooks, presentational views). Keep the viewmodel-vs-view
|
|
502
|
+
split in spirit.
|
|
503
|
+
- **PII in logs** (existing `info` debt): log ids only, never email/phone/tokens.
|
|
504
|
+
- **HubSpot as content store is net-new** (no `info` precedent). Biggest unknown is fitting the mockup's
|
|
505
|
+
rich, nested content (Q&A pool + sets, call summaries, accent `RichLine`s) into something marketing can
|
|
506
|
+
edit (§9.10). Flat strings are easy; nested arrays are the hard part.
|
|
507
|
+
- **Runtime fetch = latency + availability**: the content now depends on a HubSpot call on load. Cache
|
|
508
|
+
in the backend with short revalidation, and keep the `DEFAULT` safety net so a HubSpot hiccup or an
|
|
509
|
+
unknown slug never breaks the funnel. Mind HubSpot API rate limits.
|
|
510
|
+
- **Serializable content only**: the bundle stays pure data (no JSX/functions); behavior/icons are enum
|
|
511
|
+
keys hydrated in code, so HubSpot only ever needs to store strings/values.
|
|
512
|
+
- **No-em-dash cannot be linted on HubSpot copy**: enforce via marketing author guidance + a
|
|
513
|
+
normalization pass in the backend mapping.
|
|
514
|
+
- **DEFAULT must always render**: an unknown/absent slug (or HubSpot down) falls back cleanly, never a
|
|
515
|
+
404 funnel.
|
|
516
|
+
- **Single accent source**: every accent-tinted element routes through `var(--accent)` /
|
|
517
|
+
`color-mix`; never hardcode an accent (mockup `CLAUDE.md` rule).
|
|
518
|
+
- **Motion fidelity**: the mockup hardens transitions against stalled animation clocks (settle guards,
|
|
519
|
+
effect-owned timers). Preserve these when porting; do not "simplify" them away.
|
|
520
|
+
- **Asset weight**: full slot art/video for 3 forms × 9 slots is large; ship only what the launched
|
|
521
|
+
campaigns use.
|
|
522
|
+
|
|
523
|
+
## 11. Cleanup / fidelity checklist
|
|
524
|
+
- [ ] No hardcoded marketing copy in any component (grep-verified).
|
|
525
|
+
- [ ] No `if (campaign === …)` branches in components.
|
|
526
|
+
- [ ] No em dashes in any shipped string; single `--accent` source respected.
|
|
527
|
+
- [ ] `info` debug logging dropped; no PII in logs.
|
|
528
|
+
- [ ] `visit` route resolved (implemented or removed).
|
|
529
|
+
- [ ] Env vars documented; no secrets committed.
|
|
530
|
+
- [ ] Pixel-perfect parity pass against the mockup screens + screenshots.
|
|
531
|
+
|
|
532
|
+
## Change history
|
|
533
|
+
- 2026-07-08 — Captured the full BDR web-funnel implementation plan verbatim into the knowledge repo as a companion to the distilled `web-funnel-content-model.md` feature doc (redundancy intentional). (tcox)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Web Funnel — Next.js UI + Config-Driven Campaign Content
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: ai-bdr
|
|
5
|
+
project: AI-BDR
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: draft
|
|
9
|
+
updated: 2026-07-08
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- bdr/src/content/schema.ts
|
|
13
|
+
- bdr/src/content/hubspotProvider.ts
|
|
14
|
+
- bdr/src/content/useCampaign.ts
|
|
15
|
+
- bdr/src/content/default.ts
|
|
16
|
+
- bdr/src/server/leadSink.ts
|
|
17
|
+
- bdr/src/server/callbackService.ts
|
|
18
|
+
- bdr/src/app/api/call-now/route.ts
|
|
19
|
+
- bdr/src/app/api/call-later/route.ts
|
|
20
|
+
related:
|
|
21
|
+
- ../architecture.md
|
|
22
|
+
- call-orchestration.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What this is
|
|
26
|
+
|
|
27
|
+
The **public-facing web funnel** is the new UI / lead-capture front door of the **same
|
|
28
|
+
AI-BDR product** documented in `../architecture.md`. It is a **Next.js (App Router) +
|
|
29
|
+
TypeScript** app living in its own code repo, `bdr` (registered separately because it is a
|
|
30
|
+
distinct codebase). Only the UI/content layer is new — the funnel **reuses this AI-BDR
|
|
31
|
+
system's existing backend calls** (see "Backend reuse" below); the API/backend contracts
|
|
32
|
+
are already documented and are **not** restated here.
|
|
33
|
+
|
|
34
|
+
The UI is built pixel-perfect from the mockup at `BDR/mockup`: a **7-screen "Agent Studio"
|
|
35
|
+
flow** — Welcome → AutoBuild → Capabilities → Call Now / Schedule → Success. The
|
|
36
|
+
agent-identity model is **form × slot × accent × voice × name** (form Human/Owl/Robot ×
|
|
37
|
+
slot 0–8 × one of 5 TOGA accents → `--accent` × voice × name). Two shipped-copy rules from
|
|
38
|
+
the mockup are load-bearing: a **single `--accent` source** (every accent-tinted element
|
|
39
|
+
routes through `var(--accent)` + derived `--accent-fill`; never hardcode an accent) and
|
|
40
|
+
**no em dashes** in any shipped copy.
|
|
41
|
+
|
|
42
|
+
Full implementation plan: `BDR/PLAN.md`.
|
|
43
|
+
|
|
44
|
+
## Config-driven campaign content (HubSpot-owned, fetched by slug at runtime)
|
|
45
|
+
|
|
46
|
+
Every campaign is a **serializable data bundle** (`CampaignBundle`) keyed by a stable
|
|
47
|
+
`slug`. **Campaign content is authored by the marketing team in HubSpot** and fetched **by
|
|
48
|
+
slug at runtime, server-side**, then mapped to a `CampaignBundle`. Content is **not** stored
|
|
49
|
+
in the repo — a new campaign or a copy edit ships with **no developer and no redeploy**,
|
|
50
|
+
only a HubSpot edit.
|
|
51
|
+
|
|
52
|
+
Config-driven, per TOGA precedent (`toga25-supply`'s `useClientFields`,
|
|
53
|
+
`toga2-commerce`'s Cart C1–C7): components are **pure renderers of a bundle**; there is
|
|
54
|
+
**no `if (campaign === 'x')` branching** anywhere in the screens. The repo holds only the
|
|
55
|
+
**schema** (`CampaignBundle` types), the **HubSpot→bundle mapping** (provider), and a
|
|
56
|
+
minimal safety-net **`DEFAULT`** bundle (an engineering fallback, not authored copy).
|
|
57
|
+
|
|
58
|
+
- **Provider seam.** `CampaignContentProvider.resolve(slug)` returns a `CampaignBundle` (or
|
|
59
|
+
`DEFAULT` on an unknown/unreachable slug). `HubSpotContentProvider` runs **server-side**
|
|
60
|
+
(page server component / route handler), maps HubSpot content to a `CampaignBundle`,
|
|
61
|
+
caches it (Next revalidation), and applies the no-em-dash normalization pass. Client
|
|
62
|
+
components receive the resolved bundle as props / via `useCampaign()` and never see
|
|
63
|
+
HubSpot.
|
|
64
|
+
- **Serializable-only bundles.** Non-serializable behavior (a CTA action, an icon) is
|
|
65
|
+
referenced by an **enum key** and hydrated at the view-model layer through a
|
|
66
|
+
`Record<key, fn|Component>` registry — HubSpot only ever stores plain strings/values.
|
|
67
|
+
- **Campaign selection is URL-driven, per-request.** `resolveCampaignId()`, first match
|
|
68
|
+
wins: `?campaign=<slug>` → mapped `?hsCampaignId=<id>` (id→slug lookup so existing
|
|
69
|
+
outreach/CRM links keep working) → `DEFAULT`. Query-param entry ships first (matches how
|
|
70
|
+
leads arrive and how the ported backend already reads `?hsCampaignId=`); optional
|
|
71
|
+
`/c/[slug]` path later. No global "current campaign" state — selection is derived from
|
|
72
|
+
each visitor's URL, so any number of campaigns run **simultaneously**.
|
|
73
|
+
- **Attribution travels in the bundle.** The resolved bundle carries `hsCampaignId` and
|
|
74
|
+
`togaCampaignUuid`, used when the lead is upserted/linked and for per-campaign GA4.
|
|
75
|
+
|
|
76
|
+
### CampaignBundle shape (contract)
|
|
77
|
+
|
|
78
|
+
Keyed by `slug` (`id`). Carries `agent: AgentIdentity` (form × slot × accent → `--accent` ×
|
|
79
|
+
voice × name; the default flow does not let the user pick, so agent identity is effectively
|
|
80
|
+
campaign config), plus `welcome`, optional `landing`, `capabilities` (skill cards +
|
|
81
|
+
`anchor`/`pool`/`sets` Q&A bank + CTAs), `call`, `schedule`, `success` (`callSummaries`),
|
|
82
|
+
optional `build` (AutoBuild beats), `analytics`, and `features[]` capability flags.
|
|
83
|
+
`RichLine` supports the accent-inked span the mockup uses (e.g. `"<agent> is ready to
|
|
84
|
+
work."`). The mapping layer is the seam between HubSpot storage and this contract.
|
|
85
|
+
|
|
86
|
+
## Backend reuse (from the `info` repo; existing AI-BDR calls)
|
|
87
|
+
|
|
88
|
+
The funnel's Call Now / Schedule screens submit to a real backend **ported from the
|
|
89
|
+
external `info` repo** (github.com/agilantsolutions/info — a Next.js app whose `/bdr` route
|
|
90
|
+
already runs these calls in production). `info` is an **external reference repo, not a TOGA
|
|
91
|
+
registry repo** — port its logic, do not register or rebuild it. The ported logic drives
|
|
92
|
+
**this AI-BDR system's existing backend calls** (HubSpot contact fetch, Toga upsert +
|
|
93
|
+
campaign link, `requestCall` callback) — those contracts are already documented in
|
|
94
|
+
`call-orchestration.md` and `../architecture.md`; do **not** restate them.
|
|
95
|
+
|
|
96
|
+
Because BDR is Next.js there is no separate backend to host — all secret-bearing calls run
|
|
97
|
+
server-side (route handlers / server actions in `app/api/*`), wrapped behind two adapters
|
|
98
|
+
so only the impl changes later:
|
|
99
|
+
|
|
100
|
+
- `LeadSink.upsertLead(input)` → Toga upsert-by-email + campaign link + HubSpot.
|
|
101
|
+
- `CallbackService.requestCall(ref, whenOrNow, phone)` → Toga `requestCall`, immediate vs.
|
|
102
|
+
scheduled mapped to `CONTACT_CALL_TYPE_UUID`.
|
|
103
|
+
|
|
104
|
+
Note on "campaign": `info` uses HubSpot **only to fetch the contact**; the URL's
|
|
105
|
+
`hsCampaignId` is passed to Toga as the attribution campaign UUID
|
|
106
|
+
(`addContactToCampaign`) — attribution-only in `info`. **Fetching marketing content by
|
|
107
|
+
slug from HubSpot is net-new** to this funnel (above). The content slug and the attribution
|
|
108
|
+
UUID are related but distinct; the `CampaignBundle` carries the UUID so a slug maps to the
|
|
109
|
+
right campaign for logging.
|
|
110
|
+
|
|
111
|
+
### Environment (values live in env / Amplify config, never in the repo)
|
|
112
|
+
|
|
113
|
+
- `HUBSPOT_ACCESS_TOKEN`
|
|
114
|
+
- `TOGA_API_BASE_URL`, `TOGA_CLIENT_ID`, `TOGA_CLIENT_API_UUID`, `TOGA_CLIENT_API_SECRET`
|
|
115
|
+
- `NEXT_PUBLIC_GA_MEASUREMENT_ID`
|
|
116
|
+
|
|
117
|
+
## Gotchas
|
|
118
|
+
|
|
119
|
+
- **HubSpot content mechanism is an OPEN question that gates the content-layer phase.**
|
|
120
|
+
`info` gives no precedent (it only *reads contacts*). Candidate homes: **HubDB** (CMS-Hub
|
|
121
|
+
table — one row per campaign, `slug` column + content columns, API-fetchable; likely fit)
|
|
122
|
+
vs. a **custom object** vs. properties on the Campaigns object. The hard part is the
|
|
123
|
+
**nested content** — Q&A pool + rotation sets, call summaries, accent `RichLine`s — which
|
|
124
|
+
flat columns can't hold cleanly, so it needs child rows or a structured (JSON) field.
|
|
125
|
+
Settle with the HubSpot owner before building the content layer.
|
|
126
|
+
- **Keep secrets server-side.** `TOGA_CLIENT_API_SECRET` / `HUBSPOT_ACCESS_TOKEN` and the
|
|
127
|
+
token exchange must live in server components / route handlers, never in a `"use client"`
|
|
128
|
+
component or a `NEXT_PUBLIC_` env var (those ship to the browser). The interactive Agent
|
|
129
|
+
Studio screens are client components, so this boundary is load-bearing.
|
|
130
|
+
- **Do NOT port `info`'s debug `console.log`s** — several log PII (email/phone). Log
|
|
131
|
+
identifiers only.
|
|
132
|
+
- **Do not port `info`'s hero/curiosity marketing UI** — it is a different, older funnel.
|
|
133
|
+
- **`DEFAULT` must always render.** An unknown/absent slug or a brief HubSpot outage falls
|
|
134
|
+
back to the built-in safety net — never a 404 funnel.
|
|
135
|
+
- **Runtime fetch adds latency + an availability dependency.** Cache server-side with short
|
|
136
|
+
revalidation; mind HubSpot API rate limits.
|
|
137
|
+
- **No em dashes in any shipped copy** (mockup rule) — can't be linted on HubSpot-authored
|
|
138
|
+
copy, so enforce via marketing author guidance + the normalization pass; in-repo strings
|
|
139
|
+
get an ESLint no-em-dash check.
|
|
140
|
+
- **Single accent source** (mockup rule): every accent-tinted element routes through
|
|
141
|
+
`var(--accent)` (+ derived `--accent-fill`); never hardcode an accent value.
|
|
142
|
+
|
|
143
|
+
## Change history
|
|
144
|
+
- 2026-07-08 — Initial doc: Next.js App Router web funnel as the AI-BDR product's UI /
|
|
145
|
+
lead-capture front door; config-driven campaign bundle authored in HubSpot and fetched by
|
|
146
|
+
slug server-side at runtime (`DEFAULT` fallback, URL-driven per-request selection); backend
|
|
147
|
+
ported from the external `info` repo behind LeadSink/CallbackService adapters, reusing this
|
|
148
|
+
AI-BDR system's existing calls. HubSpot content mechanism still open. (tcox)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -27,10 +27,11 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
27
27
|
- **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
|
|
28
28
|
- **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
29
29
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
30
|
-
- **ai-bdr** (AI-BDR) —
|
|
30
|
+
- **ai-bdr** (AI-BDR) — 6 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
31
31
|
- **toga2-commerce** (TOGa Commerce) — 7 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
32
32
|
- **toga25-supply** (TOGa 2.5 Supply) — 8 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
33
33
|
- **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
34
|
+
- **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
|
34
35
|
|
|
35
36
|
## standalone framework
|
|
36
37
|
|
package/knowledge/registry.json
CHANGED
package/package.json
CHANGED