Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 2 additions & 43 deletions components/home/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Button } from "@/components/ui/button";
import { CornerBox } from "@/components/ui/corner-box";
import { Heading } from "@/components/ui/heading";
import { Text } from "@/components/ui/text";
import { TextHighlight } from "@/components/ui";
import { HomeSection } from "@/components/home/HomeSection";
import { EnterpriseLogoGrid } from "@/components/shared/EnterpriseLogoGrid";
import { cn } from "@/lib/utils";
import { HeroStatsStrip } from "@/components/home/HeroStatsStrip";
import { HeroSlogan } from "@/components/home/HeroSlogan";

export function Hero() {
return (
Expand All @@ -15,46 +13,7 @@ export function Hero() {
<HeroStatsStrip />
</CornerBox>
<CornerBox className="flex flex-col gap-4 sm:gap-8 md:gap-10 items-center px-4 py-8 sm:px-8 sm:py-10">
<Heading
as="h1"
size="big"
className={cn(
"flex-col items-center gap-0.5 sm:gap-1 md:gap-1.5 text-center font-medium leading-[105%] max-md:max-w-[500px]",
"[leading-trim:both] [text-edge:cap]",
)}
>
<TextHighlight
highlightClassName="mix-blend-multiply"
className="whitespace-nowrap"
>
Open Source<span className="inline max-[499px]:hidden">&nbsp;</span>
</TextHighlight>
<span className="flex min-[500px]:inline">
<TextHighlight
highlightClassName="mix-blend-multiply"
className="max-[499px]:pr-1.75"
>
AI
</TextHighlight>
<TextHighlight
highlightClassName="mix-blend-multiply"
className="min-[500px]:pr-2"
>
Engineering
</TextHighlight>
</span>
<TextHighlight highlightClassName="mix-blend-multiply">
Platform
</TextHighlight>
</Heading>
<Heading
as="h1"
size="big"
className={cn(
"flex sm:hidden flex-col items-center gap-1.5 text-center font-medium leading-[105%]",
"[leading-trim:both] [text-edge:cap]",
)}
></Heading>
<HeroSlogan />
<div className="flex flex-col gap-6">
<Text className="max-w-xl">
Trace and evaluate AI Agents. Collaborate with your team to
Expand Down
146 changes: 146 additions & 0 deletions components/home/HeroSlogan.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"use client";

import { useEffect, useState } from "react";
import { usePostHog } from "posthog-js/react";
import { Heading } from "@/components/ui/heading";
import { TextHighlight } from "@/components/ui";
import { cn } from "@/lib/utils";
import { usePostHogClientCapture } from "@/src/usePostHogClientCapture";

const STORAGE_KEY = "langfuse-hero-slogan-variant";

type HeroSloganVariant = "ai-engineering" | "agent-evals";

function pickVariant(): HeroSloganVariant {
return Math.random() < 0.5 ? "ai-engineering" : "agent-evals";

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 partner Custom random assignment bypasses PostHog Experiments

PostHog's experiments best practices require using getFeatureFlag() or the React equivalent useFeatureFlagVariantKey() for experiment assignment. Without this, PostHog does not record the built-in exposure event tied to an experiment flag, so those users are excluded from PostHog's experiment results and statistical significance calculations. The hero_slogan_exposure custom event captures the exposure for raw analysis, but the stated goal of "conversion analysis" in PostHog's Experiments UI won't work with this approach.

Consider migrating variant assignment to a PostHog feature flag with two variants — this provides cohort consistency, proper exposure tracking, and the Experiments results dashboard out of the box. See PostHog Experiments best practices.

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/home/HeroSlogan.tsx
Line: 14-15

Comment:
**Custom random assignment bypasses PostHog Experiments**

PostHog's experiments best practices require using `getFeatureFlag()` or the React equivalent `useFeatureFlagVariantKey()` for experiment assignment. Without this, PostHog does not record the built-in exposure event tied to an experiment flag, so those users are excluded from PostHog's experiment results and statistical significance calculations. The `hero_slogan_exposure` custom event captures the exposure for raw analysis, but the stated goal of "conversion analysis" in PostHog's Experiments UI won't work with this approach.

Consider migrating variant assignment to a PostHog feature flag with two variants — this provides cohort consistency, proper exposure tracking, and the Experiments results dashboard out of the box. See [PostHog Experiments best practices](https://posthog.com/docs/experiments/best-practices).

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

}

function readStoredVariant(): HeroSloganVariant | null {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === "ai-engineering" || stored === "agent-evals") {
return stored;
}
} catch {
// localStorage unavailable (e.g. private browsing restrictions)
}
return null;
}

function persistVariant(variant: HeroSloganVariant) {
try {
localStorage.setItem(STORAGE_KEY, variant);
} catch {
// ignore write failures
}
}

const headingClassName = cn(
"flex-col items-center gap-0.5 sm:gap-1 md:gap-1.5 text-center font-medium leading-[105%] max-md:max-w-[500px]",
"[leading-trim:both] [text-edge:cap]",
);

function AiEngineeringSlogan() {
return (
<>
<TextHighlight
highlightClassName="mix-blend-multiply"
className="whitespace-nowrap"
>
Open Source<span className="inline max-[499px]:hidden">&nbsp;</span>
</TextHighlight>
<span className="flex min-[500px]:inline">
<TextHighlight
highlightClassName="mix-blend-multiply"
className="max-[499px]:pr-1.75"
>
AI
</TextHighlight>
<TextHighlight
highlightClassName="mix-blend-multiply"
className="min-[500px]:pr-2"
>
Engineering
</TextHighlight>
</span>
<TextHighlight highlightClassName="mix-blend-multiply">
Platform
</TextHighlight>
</>
);
}

function AgentEvalsSlogan() {
const wordGap = "gap-x-1.5 sm:gap-x-2";
const phraseGap = "gap-x-3 sm:gap-x-4";

return (
<>
<TextHighlight
highlightClassName="mix-blend-multiply"
className="whitespace-nowrap"
>
Open Source
</TextHighlight>
<span
className={cn(
"flex flex-wrap justify-center items-center",
phraseGap,
)}
>
<span className={cn("inline-flex items-center", wordGap)}>
<TextHighlight highlightClassName="mix-blend-multiply">
Agent
</TextHighlight>
<TextHighlight highlightClassName="mix-blend-multiply">
Evals
</TextHighlight>
</span>
<span className={cn("inline-flex items-center", wordGap)}>
<TextHighlight highlightClassName="mix-blend-multiply">
and
</TextHighlight>
<TextHighlight highlightClassName="mix-blend-multiply">
Tracing
</TextHighlight>
</span>
</span>
</>
);
}

export function HeroSlogan() {
const posthog = usePostHog();
const capture = usePostHogClientCapture();
const [variant, setVariant] = useState<HeroSloganVariant>("ai-engineering");

useEffect(() => {
const stored = readStoredVariant();
const nextVariant = stored ?? pickVariant();
const isNewAssignment = !stored;

if (!stored) {
persistVariant(nextVariant);
}

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 partner posthog.register may fire before PostHog has finished initialising

usePostHog() returns the PostHog instance synchronously, but the underlying SDK's async bootstrap (feature-flag fetch, session init) may not have completed when this effect runs immediately after mount. Calling posthog.register() at this point can silently succeed but the super-property may not propagate to events captured in the same tick. A safer approach is to gate the registration on posthog.isFeatureEnabled or listen to the onFeatureFlags callback, ensuring the SDK is ready before registering super-properties.

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/home/HeroSlogan.tsx
Line: 125

Comment:
**`posthog.register` may fire before PostHog has finished initialising**

`usePostHog()` returns the PostHog instance synchronously, but the underlying SDK's async bootstrap (feature-flag fetch, session init) may not have completed when this effect runs immediately after mount. Calling `posthog.register()` at this point can silently succeed but the super-property may not propagate to events captured in the same tick. A safer approach is to gate the registration on `posthog.isFeatureEnabled` or listen to the `onFeatureFlags` callback, ensuring the SDK is ready before registering super-properties.

How can I resolve this? If you propose a fix, please make it concise.

setVariant(nextVariant);

posthog.register({ hero_slogan_variant: nextVariant });
capture("hero_slogan_exposure", {
variant: nextVariant,
is_new_assignment: isNewAssignment,
});

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 Visible heading flash for agent-evals users

The component hard-codes "ai-engineering" as the initial state, so every server render and first client paint shows the control variant. The useEffect then reads localStorage and may switch to "agent-evals", causing a visible flash of the wrong heading for roughly half of new visitors. For returning users assigned to "agent-evals" the flash happens on every page load.

A lazy state initialiser avoids the re-render on the client side (localStorage access is already guard-wrapped for SSR): useState<HeroSloganVariant>(() => readStoredVariant() ?? pickVariant()). You'd also want to move persistVariant into the same initialiser block so the variant is stored before any render.

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/home/HeroSlogan.tsx
Line: 112-132

Comment:
**Visible heading flash for `agent-evals` users**

The component hard-codes `"ai-engineering"` as the initial state, so every server render and first client paint shows the control variant. The `useEffect` then reads localStorage and may switch to `"agent-evals"`, causing a visible flash of the wrong heading for roughly half of new visitors. For returning users assigned to `"agent-evals"` the flash happens on every page load.

A lazy state initialiser avoids the re-render on the client side (localStorage access is already guard-wrapped for SSR): `useState<HeroSloganVariant>(() => readStoredVariant() ?? pickVariant())`. You'd also want to move `persistVariant` into the same initialiser block so the variant is stored before any render.

How can I resolve this? If you propose a fix, please make it concise.

// Track once when the hero mounts on the homepage.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Comment thread
claude[bot] marked this conversation as resolved.
Outdated

return (
<Heading as="h1" size="big" className={headingClassName}>
{variant === "ai-engineering" ? (
<AiEngineeringSlogan />
) : (
<AgentEvalsSlogan />
)}
</Heading>
);
}
4 changes: 4 additions & 0 deletions src/usePostHogClientCapture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { usePostHog } from "posthog-js/react";
// This preserves existing PostHog event structure while adding type safety
interface EventDefinitions {
copy_page: { type: "copy" | "chatgpt" | "claude" | "mcp" };
hero_slogan_exposure: {
variant: "ai-engineering" | "agent-evals";
is_new_assignment: boolean;
};
}

type EventName = keyof EventDefinitions;
Expand Down
Loading