Skip to content

feat(workflows): offer data suggestions on the domain wizard's last step - #521

Closed
Silthus wants to merge 10 commits into
feat/email-domain-wizardfrom
feat/email-domain-wizard-suggestions
Closed

Silthus wants to merge 10 commits into
feat/email-domain-wizardfrom
feat/email-domain-wizard-suggestions

Conversation

@Silthus

@Silthus Silthus commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Problem

  • A person who finishes the email domain wizard lands on a last step that offers one fixed workflow, the welcome email template, even when PostHog can suggest workflows made from their own events.
  • The workflows homepage already shows those AI suggestions behind workflows-data-suggestions. They take up to a minute to write, so loading them on the last step would leave the person waiting again.
  • The last step says "Your first sender" and "Send your first email from…" even when the project already sends from another verified domain.
  • The wizard names a new sender after the domain label before the last dot. For hello@mail.hedgebox.example.com that is "Example", and for mail.acme.co.uk it would be "Co".

Stacked on #341, the wizard stack tip. Sibling of #520, not under it.

Impact

Pays into Grow Workflows through the activation rate (measured here). Expected move: small, up. The wizard's share shows as workflows data suggestion built with surface email_domain_wizard.

Warning

🟡 Merge risk: medium · two-way door (merge-risk rubric)

  • AI generation now starts for every person who picks a domain with workflows-data-suggestions on, including people who leave at the settings step. The project cache, the generation lock, the failure cache and the AI throttles bound the spend.
  • The shared suggestions logic now sends workflows data suggestions shown when the cards render, not when the result arrives. This also changes the event for the homepage empty state and the onboarding wizard.
  • A new sender's default name is now the organization name, when it is set, not a placeholder, and plain ASCII. Recipients see it in the From line until the person edits it.
  • The new workflow chooser's "Blank workflow" card moved into a shared component. Its copy, action and create-workflow-blank data-attr stay the same.
  • Limits: the wizard sits behind workflows-email-domain-wizard, the suggestions behind workflows-data-suggestions, the backend endpoint is unchanged, and a revert restores the old behavior.

Changes

  • Loaded suggestions replace the welcome template on the last step. The step shows the homepage's AI suggestion cards, with the first card primary, when workflows-data-suggestions is on. It reuses WorkflowDataSuggestions and workflowDataSuggestionsLogic, with a new surface email_domain_wizard.
  • A "Blank workflow" card is always on the step. It is the card from the new workflow chooser, with the same copy and the same createEmptyWorkflow action. It opens a new workflow with no template.
  • Every other state keeps the welcome template. While loading, the template stays primary, with the homepage's skeletons under it. A failed, empty, ai_not_approved or unavailable answer, or the flag off, shows the template and the blank card.
  • Suggestions start loading once the person picks a domain. emailDomainLogic mounts the suggestions logic as soon as the sender exists, so the run has the settings and DNS steps to finish. It keeps the logic until the wizard closes.
    • It loads once. A tab switch to the DNS host does not restart the run.
    • It does not load with the flag off, or on the domain step before a domain exists.
  • The choices are one component, FirstWorkflowChoices. It holds the welcome template, the blank workflow card and the suggestions, and switches between the modes itself. A second step of the wizard can render it as-is, inside the same bound wizard logics.
  • The ready step's heading keeps the domain whole. It broke the domain mid-word ("hed / gebox.example.com") and switched to monospace partway through. The domain now sits in the heading's type as one inline box, so the line wraps before it. A domain wider than a whole line still wraps inside, and stays inside the card.
  • The last step says "first" only for the project's first verified sender. With another verified email sender in the project, the card title reads "Your sender" and the heading "Send email from ". While the sender list loads, the neutral copy shows.
  • The last step's lead no longer depends on the mode or on earlier workflows: "Start a workflow, and pick as the sender in its emails."
  • A new sender is named after the organization. The wizard uses the organization name when it is set and is not a placeholder: blank, "Default", "Default organization", or the signup default "'s Organization".
    • Otherwise it takes the brand label from the domain. It skips a country-code second level, so mail.acme.co.uk and acme.com.au give "Acme". The frontend has no public-suffix helper, and none was added.
    • An organization name with non-ASCII characters or "<>\ also falls back to the domain label. The email service puts the name into the From header without encoding, so such a name could break sends.
    • The person can still edit the name on the last step, as before.
  • The setup modal shows the same suggestions, because it renders the same last step.
  • The shown event fires when the cards render. The preload would otherwise count a view for people who never reach the last step.
  • Mechanical: FirstSenderCard is now SenderCard. WorkflowTemplateBlankCard is the chooser's blank card in its own file. CreateWelcomeWorkflowAction is the welcome button in its own file, byte-identical to the same file on #520. The fake email domain backend serves the suggestions endpoints. A ReadyWithSuggestions story renders the loaded state, and a ReadyLongDomain story renders the heading with a longer domain.
Mode 520px 1440px
Suggestions loaded Blank workflow card, then three suggestion cards, no welcome template Loaded suggestions, wide
Suggestions loading Welcome template, blank workflow card, and skeletons while suggestions load Loading suggestions, wide
Suggestions off Welcome template and blank workflow card Suggestions off, wide
Ready heading 520px 1440px
Before: split mid-word, monospace Heading split as "m / ail.acme.com" in monospace Heading before the fix, wide
After: mail.acme.com Heading wraps before mail.acme.com, one type style Heading after the fix, wide
After: updates.hedgebox-mail.com Heading with a longer domain on its own line Longer domain, wide
"First" copy, mail.acme.com 720px 1440px
Before: a later sender still says "first" Before: Your first sender and Send your first email, with another verified sender in the project Before: later sender, wide
After: another verified sender exists After: Your sender, Send email from mail.acme.com, Start a workflow After: later sender, wide
After: the project's first sender After: Your first sender and Send your first email from mail.acme.com After: first sender, wide

All data in the renders is invented. No horizontal overflow at either width. The loading render comes from a scratch story that is not committed.

Earlier lead, on b17d3cd: the lead changed with the mode
Mode 520px 1440px
Suggestions loaded Blank workflow card, then three suggestion cards, no welcome template Loaded suggestions, wide
Suggestions loading Welcome template, blank workflow card, and skeletons while suggestions load Loading suggestions, wide
Suggestions off Welcome template and blank workflow card Suggestions off, wide
Earlier layout, on 447d6ad: suggestions under the welcome template
Suggestions off (520px) Suggestions on (520px)
Last step with the welcome email template only Last step with the welcome template and three suggestion cards below it
Suggestions off (1440px) Suggestions on (1440px)
Last step with the welcome email template only, wide Last step with the welcome template and three suggestion cards, wide

How did you test this code?

Test seams:

  • emailDomainLogic, the preload. emailDomainLogic.test.ts drives the fake backend from the settings phase through the wait for DNS phase to ready, flag on and off. It catches a preload that starts later than the settings step, a second load, and a load with the flag off. The domain step test catches a load before a domain exists. It failed on the settings step before this change.
  • The rendered last step, loaded. EmailDomainScene.test.tsx goes from checking DNS to the ready step. The cards show, the welcome template is gone, the generate call ran once, and the shown event fires only on the ready step. "Blank workflow" opens the new workflow page without a template. It failed before the change.
  • The "first" copy, rendered. EmailDomainScene.test.tsx: the manual-path test now asserts "Your first sender" and "Send your first email from mail.acme.com". A new case seeds a second verified sender on another domain and asserts "Your sender", "Send email from mail.acme.com" and no "first" anywhere on the page. It failed before the change.
  • The default sender name, through emailDomainWizardLogic. One parameterized case per input in emailDomainWizardLogic.test.ts: the organization name, a trimmed one, a blank one, the signup placeholder on shop.acme.co.uk, "Default" on acme.com.au, a non-ASCII name and a quoted name. Each asserts the name in the create request the fake backend receives. Four rows failed before the change, the last two before the review fix.
  • The fallback. One parameterized case per state: flag off, no suggestions, a failed request, a pending request, and a generating answer. Each asserts that the template stays primary and opens its template, and that the blank card shows.

Live run on 64451f01bdf (evidence kept in the fleet proof store):

  • The real workflow_data_suggestions view, through the Django test client, with the exact calls the wizard makes. The LLM and the event classifier are stubbed, Celery is eager. The backend is unchanged since that run.
  • Backend flag off: 403, which the frontend loader turns into the template.
  • Not approved: ai_not_approved. No event classifier: unavailable.
  • Approved: generating, then ready with cards, while the sender is still unverified, as on the settings step. The ready step's reads hit the cache, so the LLM ran once.
  • After the sender verifies: email_sender_ready flips to true. "Create workflow" on the preloaded card builds a draft whose email step sends from the new sender, with no missing inputs.
  • The three modes above, rendered from Storybook in a headless browser.
  • The ready heading, before and after, with mail.acme.com and updates.hedgebox-mail.com. A 63-character label at 520px stays inside the card, measured in the live Storybook page (the span ended at 859px against a 504px card with break-words, and at 471px with wrap-anywhere).

Checks: the Channels/EmailDomain Jest suites pass on 907d522a6de (92 tests), and the setupGuide and Workflows/templates suites passed on 64451f01bdf. The frontend typecheck reports nine errors, all outside this diff: AgentPromptButton.tsx and seven files under products/conversations/frontend/components/QuillSidePanel/.

Not checked:

  • A real LLM and event classifier. A preview box needs workflows-data-suggestions on, the org's AI data processing approved, the Jev event classifier configured, and LLM gateway keys. Without them the step shows the template and the blank card.
  • The setup modal host. It renders the same last step, but no render covers it.
  • A live run on 907d522a6de. The owed live-run proof is reused from 64451f01bdf. A later redeploy of the preview box re-runs it.
  • The new name rule in a render. The stories seed a sender that already has a name, so only the logic test covers the rule.

The heading fix has no Jest test, because jsdom does no layout. The ReadyLongDomain story's visual review baseline guards it.

Test rationale: The logic seam guards when the preload starts and that it runs once, which no existing test covers. The scene seam guards what the person sees in each mode, the blank fallback and the shown event, which only a render shows. The existing workflowDataSuggestionsLogic tests still cover polling and building.

Release status

  • This change is behind a feature flag and is not available to users

The wizard sits behind workflows-email-domain-wizard, the suggestions behind workflows-data-suggestions. Two changes reach other surfaces: the shown event timing (behind workflows-data-suggestions too) and the shared blank card in the new workflow chooser, which behaves as before.

Docs update

None.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code in T3 Code, Claude Opus 5.5 (high). Design pass, reviews and final read by GPT-6.1 Sol (high).

  • Skills: impact, tdd, writing-tests, writing-kea-logics, using-kea-disposables, writing-ui-components, writing-user-facing-copy, pr-description, writing-pr-descriptions, merge-risk, the QA swarm design pass.
  • The first version put the suggestions under the welcome template. Michael chose to replace the template once suggestions load, with a blank workflow fallback.
  • The design pass found two items, both fixed in 447d6adfea6: the preload counted a shown event during DNS checks (major), and no test covered a pending last step (minor).
  • Review round 1 on 447d6adfea6 found nothing.
  • Review round 2 on 6e364f74830 found one minor item, fixed in b17d3cd5d17: the lead swapped its bare text nodes when suggestions loaded, which a page translation extension can leave stale (frontend/src/AGENTS.md Rule 7). Each lead is now its own keyed paragraph.
  • The final read on b17d3cd5d17 found nothing.
  • Michael then asked for the preload to start once the domain is picked, and for the choices to be one component that the wait for DNS step on feat(workflows): the domain wizard prompts for send once verified #520 can render. Done in a955d415985 and bcee93148d3.
  • Review round 3 on bcee93148d3 found nothing and graded the risk medium, for the earlier generation. The proof store's two-round budget was lifted with Michael's request for another round. No fixes followed, so no final read ran.
  • A demo recording then found the ready heading splitting the domain mid-word. Fixed in dfb66cc960c. The final read on it found one medium item: a domain longer than the card overflowed with break-words. Fixed in 64451f01bdf with wrap-anywhere. The final read on 64451f01bdf found nothing. fleet proof gate passes on HEAD.
  • Michael then asked for two fixes here, so the base stack is not restacked: "first" copy on a later sender, and a better default sender name. Done in 6d970b17340. The design pass on it found nothing.
  • Review round 4 on 6d970b17340 found two items. Fixed: a non-ASCII or quoted organization name would reach the From header unencoded (major), fixed in 907d522a6de. Rejected: shop.net.de reads as a country-code suffix (minor). It needs a domain whose own label is net or co under a country code, the label there is a generic word either way, and the name stays editable. The round-4 reservation used the task brief's request for one more round. The final read on 907d522a6de found nothing. fleet proof gate passes on HEAD.
  • Follow-up: the email service sends every stored sender name without MIME encoding or escaping, whatever its source. A typed non-ASCII name already hits it. That is a separate fix outside this PR.
  • Follow-up: the new ReadyWithSuggestions and ReadyLongDomain stories add visual review baselines that need approval. The Ready story changes, because it gains the blank workflow card.

Decisions

Question Answer Who Where
Template once suggestions load Loaded suggestions replace the welcome template, with a blank workflow fallback always on the step. Replaced: suggestions under the template 👤 Human session, not posted
Setup modal host Shows the same suggestions as the page 👤 Human session, not posted
When the preload starts Once the person picks a domain. Replaced: at the wait for DNS step 👤 Human session, not posted
One block for two places FirstWorkflowChoices, rendered on the last step here and on the wait for DNS step by a follow-up on #520 👤 Human session, not posted
Where the copy and name fixes land On this PR, not in the base stack, so #324 to #341 are not restacked 👤 Human session, not posted
Default sender name The organization name unless it is a placeholder, then the domain's brand label, as the task brief recommended after a check against the code 🤖 Agent session, not posted
What "first" means No other verified email sender in the project. Neutral copy while the list loads 🤖 Agent session, not posted
Brand label without a suffix helper Skip a generic second level (co, com, org, net, ac, gov, edu) under a two-letter top level. Alternative: a public-suffix dependency, ruled out 🤖 Agent session, not posted
Organization names the From header can't carry Fall back to the domain label. Alternative: encode at the email service, left as a separate fix 🤖 Agent session, not posted
What "suggestions on" means The workflows-data-suggestions flag, the homepage's gate. The backend adds the AI data processing check and answers ai_not_approved 🤖 Agent session, not posted
Which blank fallback The "Blank workflow" card from the new workflow chooser, shared, placed before the suggestions as the homepage places its plain options 🤖 Agent session, not posted
When the shown event fires When the cards render, on every surface. Alternative: keep it on result arrival and skip the wizard surface 🤖 Agent session, not posted

Involvement: no grilling rounds, no prototypes. Reviews: two design passes, four review rounds and four final reads (GPT-6.1 Sol). Proof captured: live run and renders.

🤖 Generated with Claude Code

Silthus and others added 2 commits October 20, 2026 04:51
The wizard's ready step shows the AI workflow suggestions from the
workflows homepage behind workflows-data-suggestions. They start loading
when the person reaches the wait for DNS phase and load once. The
welcome email template stays as the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The domain wizard now loads suggestions before its last step is on
screen, so a finished load no longer counts as a view. The component
that renders the cards reports the shown event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Silthus Silthus self-assigned this Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

🤖 CI report

✅ Comment density — 3% of added code lines are comments (12 of 456)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
products/workflows/frontend/Channels/EmailDomain/components/FirstWorkflowChoices.tsx 3 31
products/workflows/frontend/Channels/EmailDomain/__mocks__/fakeEmailDomainBackend.ts 2 21
products/workflows/frontend/Channels/EmailDomain/emailDomainLogic.ts 2 57
products/workflows/frontend/setupGuide/suggestions/workflowDataSuggestionsLogic.ts 2 18
products/workflows/frontend/Channels/EmailDomain/components/CreateWelcomeWorkflowAction.tsx 1 17
products/workflows/frontend/Channels/EmailDomain/emailDomainWizardLogic.ts 1 31
products/workflows/frontend/Workflows/templates/WorkflowTemplateBlankCard.tsx 1 17

This check does not block merging. It updates on every push and clears when the share drops.

Silthus and others added 6 commits October 20, 2026 05:39
…gestions

Once the AI suggestions load on the wizard's last step, they replace the
welcome email template. A blank workflow card, the one from the new
workflow chooser, stays on the step in every mode for people who want
neither. The chooser and the wizard now share that card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page translation extension replaces text nodes, so the lead swaps its
paragraph rather than its text when the suggestions load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The suggestions take up to a minute to write, so the domain wizard now
starts them as soon as the sender exists rather than at the wait for DNS
step. They still load once, and only with workflows-data-suggestions on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FirstWorkflowChoices holds the suggestions, the welcome template and the
blank workflow card, so a second step of the domain wizard can render
the same block. The last step's lead no longer depends on the mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The heading broke the domain mid-word and switched to monospace partway
through. The domain now sits in the heading's type as one inline box, so
the line wraps before it. A story renders the step with a longer domain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
break-words keeps a long label at its full minimum width, so a domain
wider than the card overflowed it. wrap-anywhere lets that box shrink,
and a domain that fits on a line still wraps whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus and others added 2 commits October 20, 2026 10:22
…r the organization

The ready step said "Your first sender" and "Send your first email" even when
the project already sends from another verified domain. It now says first only
when no other email sender is verified.

The wizard named a new sender after the label before the domain's suffix, so
mail.acme.co.uk became "Co". It now uses the organization name, unless it is
a placeholder such as "Jane's Organization". Otherwise it takes the brand
label and skips a country-code second level such as co.uk or com.au.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… header can't carry it

The email service puts the sender name into the From header as is, without
MIME encoding or escaping. An organization name with non-ASCII characters or
quotes would break that header, so the wizard falls back to the domain's brand
label for such names. A person can still type any name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant