Skip to content

fix(lifecycle_emails): map experiment action_taken to friendly text - #74

Merged
mastermanas805 merged 2 commits into
masterfrom
fix/email-experiment-friendly-action
May 31, 2026
Merged

fix(lifecycle_emails): map experiment action_taken to friendly text#74
mastermanas805 merged 2 commits into
masterfrom
fix/email-experiment-friendly-action

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Summary

Same bug class as worker#73 (make_permanent_endpoint): the worker email template was reading params["action_taken"] from the dashboard's /experiments/converted call and inlining the raw snake_case identifier straight into the user-facing body.

Users would receive:

Thanks for trying the new instanode experience. We noticed you checkout_started — nice.

Experiment: upgrade_button (B)

— exposing the dashboard's internal action constant + the experiments registry id as if they were English phrases.

What changed

  • New friendlyExperimentAction() helper (mirrors friendlyMakePermanentSource) maps every dashboard/instanode-web emit value to a user-facing phrase. Unknown values → empty string → template clause drops entirely.
  • New friendlyExperimentName() helper does the same for {{ .Experiment }}.
  • renderExperimentConversion routes both fields through the helpers.

Mappings shipped:

field enum friendly text
action_taken checkout_started started the checkout flow
action_taken overview_upgrade_clicked clicked Upgrade on the dashboard
experiment upgrade_button the Upgrade button rollout
experiment onboarding_v2 the new onboarding flow

Regression guards (CLAUDE.md rule 18 — registry-iterating)

  • TestRenderExperimentConversion_ActionIsFriendly — enumerates every action value emitted by dashboard/src/components/UpgradeButton.tsx, dashboard/src/pages/OverviewPage.tsx, and the instanode-web siblings. A future emitter that adds a value without a friendly mapping fails this test before the broken copy reaches a real inbox.
  • TestRenderExperimentConversion_UnknownActionDropsClause — safety net: a brand-new unmapped action drops the "We noticed you X — nice." clause entirely rather than leaking the raw enum.
  • TestRenderExperimentConversion_ExperimentNameIsFriendly — same shape for the experiment-id field.

Coverage block

Symptom:        "We noticed you checkout_started — nice." in email
Enumeration:    grep -rn '"action_taken"' api/ worker/
                grep -rn 'action:\s*'"'"'[a-z_]' dashboard/src instanode-web/src
Sites found:    2 emit values (checkout_started, overview_upgrade_clicked) across 4 call sites
Sites touched:  2 (both mapped in friendlyExperimentAction)
Coverage test:  TestRenderExperimentConversion_ActionIsFriendly + ExperimentNameIsFriendly + UnknownActionDropsClause
Live verified:  awaiting user verification — emit path requires a real /experiments/converted call from a logged-in dashboard session. Renderer-level test green:
                  ok  	instant.dev/worker/internal/jobs	1.349s
                make gate green locally.

Test plan

  • make gate green locally
  • go test ./internal/jobs/ -run 'TestRenderExperimentConversion' → PASS (3/3)
  • go test ./internal/jobs/ -run 'TestLifecycleEmail|TestEveryEmailKindHasAGoRenderer' → still green (regression unchanged)
  • CI green
  • Post-merge: trigger a real /experiments/converted from the dashboard UpgradeButton, confirm the resulting email body says "started the checkout flow" not "checkout_started"

Same bug class as worker#73 (make_permanent_endpoint): the worker email
template was reading params["action_taken"] from the dashboard's
experiments.converted call and inlining the raw snake_case identifier
straight into the body. Users would receive

    "We noticed you checkout_started — nice."

in their inbox — exposing the dashboard's internal action constant as
if it were a past-tense English phrase.

Fix mirrors friendlyMakePermanentSource: a friendlyExperimentAction
helper maps each emit-site value to a user-facing phrase; unknown
values render as empty so the surrounding template clause disappears
entirely (better than leaking a future snake_case enum). Same treatment
for the {{ .Experiment }} field (the upgrade_button id was leaking into
"Experiment: upgrade_button (B)").

Mappings shipped:
- action_taken
  - "checkout_started"         → "started the checkout flow"
  - "overview_upgrade_clicked" → "clicked Upgrade on the dashboard"
- experiment
  - "upgrade_button"           → "the Upgrade button rollout"
  - "onboarding_v2"            → "the new onboarding flow"

Registry-iterating regression tests (CLAUDE.md rule 18) enumerate every
emit site live in dashboard + instanode-web today; a future emitter that
adds a value without a friendly mapping fails the test before reaching
production. Plus a safety-net test asserting an unknown action drops
the clause entirely rather than leaking the raw enum.

Coverage block:
  Symptom:        "We noticed you checkout_started — nice." in email
  Enumeration:    grep -rn '"action_taken"' api/ worker/ ; grep -rn 'action:\s*'"'"'[a-z_]' dashboard/src instanode-web/src
  Sites found:    2 emit values (checkout_started, overview_upgrade_clicked) from 4 call sites
  Sites touched:  2 (both mapped in friendlyExperimentAction)
  Coverage test:  TestRenderExperimentConversion_ActionIsFriendly enumerates emit values; TestRenderExperimentConversion_UnknownActionDropsClause guards the safety net
  Live verified:  awaiting user verification — emit path requires a real /experiments/converted call from a logged-in dashboard session; local renderer test green (ok instant.dev/worker/internal/jobs 1.349s)
@mastermanas805
mastermanas805 force-pushed the fix/email-experiment-friendly-action branch from 2575df8 to fc6efbf Compare May 31, 2026 05:48
@mastermanas805
mastermanas805 merged commit 436b916 into master May 31, 2026
11 checks passed
@mastermanas805
mastermanas805 deleted the fix/email-experiment-friendly-action branch May 31, 2026 06:01
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