Skip to content

Feat/statsig best cta#2953

Merged
eldadfux merged 2 commits intomainfrom
feat/statsig-best-cta
May 1, 2026
Merged

Feat/statsig best cta#2953
eldadfux merged 2 commits intomainfrom
feat/statsig-best-cta

Conversation

@eldadfux
Copy link
Copy Markdown
Member

@eldadfux eldadfux commented May 1, 2026

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

eldadfux added 2 commits May 1, 2026 06:21
Add Statsig experiment best_cta with cta param for hero primary button.
Wire SSR bundle, bootstrap, client exposure, and hero_cta URL override.
Align desktop nav, mobile header, and custom hero with heroCta from load.

Made-with: Cursor
- Implemented `heroCtaIfShortStartBuilding` function to dynamically adjust CTA labels based on the `heroCta` value from the page state.
- Updated various components including PreFooter, custom hero, and pricing pages to utilize the new CTA logic, ensuring consistency in messaging.
- Enhanced blog mid CTA handling to sync with the latest hero experiment CTA values.
- Refactored imports and improved code organization for better readability.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Greptile Summary

This PR introduces a best_cta Statsig experiment that propagates a configurable CTA label (defaulting to 'Start building for free') across the homepage hero, site header, mobile nav, pricing cards, blog mid-CTAs, and several other surfaces via a new heroCta page-data field and a heroCtaIfShortStartBuilding helper.

  • Unconditional copy change on non-marketing routes: page.data.heroCta is never set on /pricing, /cloud-ga, and the compare-plans component, so navCtaLabel always resolves to DEFAULT_HERO_CTA ('Start building for free'). Every heroCtaIfShortStartBuilding('Start building', navCtaLabel) call therefore replaces the previous 'Start building' with 'Start building for free' regardless of whether any Statsig experiment is active — this is an unconditional copy change on those pages.
  • PreFooter silently removed from /pricing with no explanation; if intentional it should be documented.

Confidence Score: 3/5

Not safe to merge as-is — the pricing and cloud-ga pages have a real unconditional copy regression and a silent UI removal.

Two P1 findings: (1) CTA text on pricing/cloud-ga/compare-plans pages changes unconditionally from 'Start building' to 'Start building for free' without an experiment running, because those routes never set heroCta in page data; (2) PreFooter is silently removed from the pricing page. The Statsig integration code itself is implemented correctly.

src/routes/pricing/+page.svelte, src/routes/pricing/compare-plans.svelte, and src/routes/cloud-ga/(components)/call-to-action.svelte all need attention for the unconditional fallback issue.

Important Files Changed

Filename Overview
src/lib/statsig/hero-query-overrides.ts Adds heroCta field to HeroQueryBaseline/Resolved, normalizeHeroCta, readHeroCtaOverride, and heroCtaIfShortStartBuilding helper; logic is consistent with existing subtitle/title patterns.
src/lib/statsig/experiments/marketing-hero-server.ts Adds evaluateHeroCtaWithClient and evaluateHeroCtaExperiment; correctly plumbed into loadMarketingHomeStatsigBundle Promise.all and bootstrap filter set.
src/lib/statsig/experiments/marketing-hero-client.ts Extends MarketingHeroStatsigBaseline and exposure return type with heroCta; reads best_cta.cta param and normalises it correctly.
src/routes/(marketing)/+page.server.ts Passes heroCta fallback into loadMarketingHomeStatsigBundle, applies query overrides, and returns heroCta in page data — SSR path looks correct.
src/routes/pricing/+page.svelte Two issues: (1) page.data.heroCta is always undefined on this route, so all three plan CTAs unconditionally switch from 'Start building' to 'Start building for free'; (2) PreFooter is silently removed.
src/routes/pricing/compare-plans.svelte Same unconditional CTA fallback issue as +page.svelte; three 'Start building' buttons now always show 'Start building for free' regardless of experiment state.
src/routes/cloud-ga/(components)/call-to-action.svelte Reads heroCta from page data but cloud-ga route never sets it, so the CTA unconditionally shows 'Start building for free' instead of 'Start building'.
src/lib/components/IsLoggedIn.svelte Derives nav CTA from page data with correct fallback; no behaviour change on non-marketing routes since the fallback matches the previously hardcoded string.
src/lib/components/PreFooter.svelte Applies heroCtaIfShortStartBuilding to plan button text using page data CTA; correct on routes that expose heroCta, but will show 'Start building for free' on all other routes.
src/routes/(marketing)/(components)/hero.svelte Adds heroCta to resolveHeroQueryOverrides baseline and renders resolved.heroCta; marketing-only, so heroCta is always populated from server data.
src/lib/utils/blog-mid-cta.ts Accepts optional heroExperimentCta and applies heroCtaIfShortStartBuilding to auto-generated and manual CTA labels; logic is sound.
src/markdoc/layouts/Post.svelte Passes page.data.heroCta ?? DEFAULT_HERO_CTA into prepareBlogCtaState; will show the default on non-marketing blog posts, which is the intended fallback.

Reviews (1): Last reviewed commit: "feat: integrate hero CTA updates across ..." | Re-trigger Greptile

Comment on lines +17 to +19
const navCtaLabel = $derived(
(page.data as { heroCta?: string | null }).heroCta ?? DEFAULT_HERO_CTA
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CTA text changed unconditionally on pricing page

The pricing page has no server load that sets heroCta, so page.data.heroCta is always undefined here. navCtaLabel therefore always resolves to DEFAULT_HERO_CTA ('Start building for free'). Every call to heroCtaIfShortStartBuilding('Start building', navCtaLabel) then returns 'Start building for free', changing all three plan buttons on this page from the previous 'Start building' to 'Start building for free' regardless of whether any Statsig experiment is active. The same issue affects compare-plans.svelte and call-to-action.svelte (cloud-ga route), both of which have no heroCta in their route data.

Comment on lines 435 to 436
</div>
<div class="web-big-padding-section-level-2 relative !mb-0">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 PreFooter silently removed from pricing page

PreFooter is dropped here without explanation. The component renders a pricing-plan card strip and a CTA section that visitors landing on /pricing previously saw. If this removal is intentional (e.g. to avoid duplicating plan cards), it should be documented in the PR description or a comment — as-is it looks like an accidental deletion.

Comment on lines 21 to 23
</script>

<Button
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Repeated inline page.data type cast across 8+ components

The pattern (page.data as { heroCta?: string | null }).heroCta ?? DEFAULT_HERO_CTA is copied verbatim into IsLoggedIn.svelte, PreFooter.svelte, site-header.svelte, pricing.svelte (marketing), pricing/+page.svelte, compare-plans.svelte, call-to-action.svelte, and Post.svelte. A single shared type or a tiny helper (e.g. getPageHeroCta(page)) would make the intent clear and prevent one component from silently drifting if the field is ever renamed.

@eldadfux eldadfux merged commit 01436b0 into main May 1, 2026
6 checks passed
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