diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0967ef424bce..45014afcd5a5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1 +1,5 @@ -{} +{ + "permissions": { + "defaultMode": "auto" + } +} diff --git a/src/components/Code/FlowDiagram.tsx b/src/components/Code/FlowDiagram.tsx index 8c4ee1b46b06..56e9472376f2 100644 --- a/src/components/Code/FlowDiagram.tsx +++ b/src/components/Code/FlowDiagram.tsx @@ -2,12 +2,21 @@ import React, { useRef, useState, useEffect } from 'react' import { IconActivity, IconLightBulb, IconMessage, IconSparkles, IconPullRequest } from '@posthog/icons' import { usePrefersReducedMotion } from './usePrefersReducedMotion' -const steps = [ - { label: '1. Analyze\nusage', icon: IconActivity, actor: 'Human' as const }, - { label: '2. Decide what\nto build', icon: IconLightBulb, actor: 'Human' as const }, - { label: '3. Prompt &\ncontext', icon: IconMessage, actor: 'Human' as const }, - { label: '4. Build', icon: IconSparkles, actor: 'Machine' as const }, - { label: '5. Ship', icon: IconPullRequest, actor: 'Human' as const }, +export interface FlowStep { + /** Step label. `\n` is preserved as a soft break in the grid layout and flattened to a space in the list layout. */ + label: string + /** Optional secondary text shown after the label in the list layout (muted). */ + description?: string + icon: React.ComponentType<{ className?: string }> + actor: 'Human' | 'Machine' +} + +const defaultSteps: FlowStep[] = [ + { label: '1. Analyze\nusage', icon: IconActivity, actor: 'Human' }, + { label: '2. Decide what\nto build', icon: IconLightBulb, actor: 'Human' }, + { label: '3. Prompt &\ncontext', icon: IconMessage, actor: 'Human' }, + { label: '4. Build', icon: IconSparkles, actor: 'Machine' }, + { label: '5. Ship', icon: IconPullRequest, actor: 'Human' }, ] const actorColors: Record = { @@ -17,9 +26,20 @@ const actorColors: Record = { interface FlowDiagramProps { className?: string + /** Ordered steps rendered in the card. Defaults to the "Coding with AI" flow. */ + steps?: FlowStep[] + /** Left-aligned header label (uppercased via CSS). */ + headerLeft?: string + /** Right-aligned header label (uppercased via CSS). */ + headerRight?: string } -export function FlowDiagram({ className = '' }: FlowDiagramProps) { +export function FlowDiagram({ + className = '', + steps = defaultSteps, + headerLeft = 'Coding with AI', + headerRight = '(cir. 2022-2025)', +}: FlowDiagramProps) { const ref = useRef(null) const [isVisible, setIsVisible] = useState(false) const prefersReducedMotion = usePrefersReducedMotion() @@ -47,8 +67,8 @@ export function FlowDiagram({ className = '' }: FlowDiagramProps) { return (
- Coding with AI - (cir. 2022-2025) + {headerLeft} + {headerRight}
{/* Mobile: stacked list */}
@@ -63,10 +83,17 @@ export function FlowDiagram({ className = '' }: FlowDiagramProps) { }} > - - {step.label.replace('\n', ' ')} + + + {step.label.replace('\n', ' ')} + + {step.description && {step.description}} - + {step.actor}
diff --git a/src/components/Products/ReaderViewProduct/templates/Overview.tsx b/src/components/Products/ReaderViewProduct/templates/Overview.tsx index 4abe516b246b..83f36cc6bb8d 100644 --- a/src/components/Products/ReaderViewProduct/templates/Overview.tsx +++ b/src/components/Products/ReaderViewProduct/templates/Overview.tsx @@ -20,16 +20,26 @@ const Overview = ({ id, productData }: SectionComponentProps) => { -
+ {screenshots.home.srcDark && ( -
+ )} + {hogs?.default?.src && ( +
+ +
+ )} )} diff --git a/src/components/Products/ReaderViewProduct/templates/PricingFooterCTA.tsx b/src/components/Products/ReaderViewProduct/templates/PricingFooterCTA.tsx index 34d3a135319e..9569a8a39484 100644 --- a/src/components/Products/ReaderViewProduct/templates/PricingFooterCTA.tsx +++ b/src/components/Products/ReaderViewProduct/templates/PricingFooterCTA.tsx @@ -15,12 +15,12 @@ const PricingFooterCTA = ({ id, productData }: SectionComponentProps) => { The hedgehog has been waiting this whole time.

- It's free to start. Not "free trial" free — actually free. No card, no call, no "someone from + It's free to start. Not "free trial" free – actually free. No card, no call, no "someone from our team will be in touch." Just PostHog.

- Get started — free + Get started – free Talk to a human diff --git a/src/components/ReplayVision/AIPromptsSection.tsx b/src/components/ReplayVision/AIPromptsSection.tsx new file mode 100644 index 000000000000..61444871df69 --- /dev/null +++ b/src/components/ReplayVision/AIPromptsSection.tsx @@ -0,0 +1,169 @@ +import React, { useState } from 'react' +import CloudinaryImage from 'components/CloudinaryImage' +import { ToggleGroup } from 'components/RadixUI/ToggleGroup' +import { LabeledList } from 'components/Products/ReaderViewProduct/helpers' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' + +const INTRO = + 'Ask your AI agent to author scanners, scan a session, or read what Replay Vision found – without leaving your editor. Works in any MCP client via the PostHog MCP: Cursor, Claude Code, Codex, VS Code, and more.' + +// Detective hedgehog surveilling a wall of monitors – sits where Session +// Replay's "AI prompts" section floats its hog. +const IMAGE = + 'https://res.cloudinary.com/dmukukwp6/image/upload/noir_desk_relax_surveillance_computer_63a434c398.png' as const + +interface PromptGroup { + title: string + tools: string[] + prompts: string[] +} + +const promptGroups: PromptGroup[] = [ + { + title: 'Create a scanner', + tools: ['vision-scanners-create'], + prompts: [ + 'Create a Replay Vision scanner that flags sessions where users get stuck on the /checkout page, narrow to enterprise customers, and start at 25% sampling. Estimate the cost first.', + 'Set up a summarizer scanner for my onboarding flow.', + 'Make a scanner that scores how frustrated users are on the /pricing page.', + ], + }, + { + title: 'Size it before you commit', + tools: ['vision-scanners-estimate-create', 'vision-quota-retrieve'], + prompts: [ + 'Estimate how many observations that scanner would produce this month before creating it.', + 'How much Replay Vision quota do we have left this month?', + ], + }, + { + title: 'Scan a session on demand', + tools: ['vision-scanners-scan-session'], + prompts: [ + "Scan session abc123 with the 'Dead-end pages' scanner and tell me what it found, including the reasoning.", + ], + }, + { + title: 'Read observations', + tools: ['vision-observations-list', 'vision-scanners-observations-list'], + prompts: [ + 'Find every Replay Vision observation for session abc123 and give me a one-line summary of each.', + "List the last 20 observations from my 'Frustration score' scanner and summarize what's driving high scores.", + ], + }, + { + title: 'Tune a scanner', + tools: ['vision-scanners-list', 'vision-scanners-update'], + prompts: [ + 'List all my scanners and show me the config for the frustration one.', + "Bump the 'Dead-end pages' scanner to 50% sampling.", + ], + }, +] + +const toolGroups: { job: string; tools: string[] }[] = [ + { + job: 'Author scanners', + tools: [ + 'vision-scanners-list', + 'vision-scanners-get', + 'vision-scanners-create', + 'vision-scanners-update', + 'vision-scanners-delete', + ], + }, + { + job: 'Size before creating', + tools: ['vision-scanners-estimate-create', 'vision-quota-retrieve'], + }, + { + job: 'Scan on demand', + tools: ['vision-scanners-scan-session'], + }, + { + job: 'Read observations', + tools: [ + 'vision-observations-list', + 'vision-observations-retrieve', + 'vision-scanners-observations-list', + 'vision-scanners-observations-get', + ], + }, +] + +const AIPromptsSection = ({ id }: SectionComponentProps) => { + const [tab, setTab] = useState<'prompts' | 'tools'>('prompts') + + return ( +
+

AI prompts

+

{INTRO}

+ { + if (value === 'prompts' || value === 'tools') setTab(value) + }} + options={[ + { label: 'Example prompts', value: 'prompts', default: true }, + { label: 'Tools', value: 'tools' }, + ]} + className="mb-4 max-w-sm" + /> +
+
+ {tab === 'prompts' && ( + <> + +
+ {promptGroups.map((g) => ( +
+

+ {g.title} + + {g.tools.join(' · ')} + +

+
    + {g.prompts.map((p) => ( +
  • “{p}”
  • + ))} +
+
+ ))} +
+ + )} + + {tab === 'tools' && ( + <> +

+ All Replay Vision MCP tools are prefixed{' '} + vision-: +

+
+ ({ + label: g.job, + description: ( + {g.tools.join(' · ')} + ), + }))} + /> +
+ + )} +
+
+
+ ) +} + +export default AIPromptsSection diff --git a/src/components/ReplayVision/HowToUseSection.tsx b/src/components/ReplayVision/HowToUseSection.tsx new file mode 100644 index 000000000000..31c06a5d5e16 --- /dev/null +++ b/src/components/ReplayVision/HowToUseSection.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import TabbedCarousel from 'components/TabbedCarousel' +import CarouselSlide from 'components/Products/ReaderViewProduct/CarouselSlide' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' +import { applications } from 'hooks/productData/replay_vision/slides' + +// "How do I use it?" – renders the applications carousel in the shared +// TabbedCarousel format (same primitives as the Applications template), with +// Replay Vision's own heading and intro copy. +const HowToUseSection = ({ id, productData }: SectionComponentProps) => { + if (!applications.length) return null + + return ( +
+

How do I use it?

+

There are a few ways to put Replay Vision to work.

+ ({ + value: s.slug, + label: s.label, + icon: s.icon, + color: s.color, + activeText: s.activeText, + progressBar: s.progressBar, + content: , + }))} + slideDuration={6000} + showActiveBg={false} + slideClassName="!min-h-0 !p-0 !rounded" + className="mt-4 mb-12" + /> +
+ ) +} + +export default HowToUseSection diff --git a/src/components/ReplayVision/OldWaySection.tsx b/src/components/ReplayVision/OldWaySection.tsx new file mode 100644 index 000000000000..86b0de787de7 --- /dev/null +++ b/src/components/ReplayVision/OldWaySection.tsx @@ -0,0 +1,70 @@ +import React, { useState } from 'react' +import { IconWarning, IconRewindPlay, IconSearch, IconStethoscope, IconCode, IconPullRequest } from '@posthog/icons' +import { StickerTombstone } from 'components/Stickers/Stickers' +import { ChoppyReveal } from 'components/Code/ChoppyReveal' +import { RoughAnnotation } from 'components/Code/RoughAnnotation' +import { FlowDiagram, type FlowStep } from 'components/Code/FlowDiagram' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' +import { SectionLabel, InlineIcon, KeyBadge } from './sectionHelpers' + +// The manual, pre-Replay-Vision workflow. Every step is on you — the tool just +// hands you the footage. +const steps: FlowStep[] = [ + { label: "1. Notice\nsomething's off", icon: IconWarning, actor: 'Human' }, + { label: '2. Watch hours\nof sessions', icon: IconRewindPlay, actor: 'Human' }, + { label: '3. Spot the\npattern', icon: IconSearch, actor: 'Human' }, + { label: '4. Diagnose\nthe cause', icon: IconStethoscope, actor: 'Human' }, + { label: '5. Write\nthe fix', icon: IconCode, actor: 'Human' }, + { label: '6. Ship', icon: IconPullRequest, actor: 'Human' }, +] + +const OldWaySection = ({ id }: SectionComponentProps) => { + const [p1Done, setP1Done] = useState(false) + + return ( +
+ + + + The old way to use session + replay + + +

+ setP1Done(true)}> + {'Session replay tells you '} + + what happened + + {', but only if you press '} + + Play + {' '} + {'on session after session after session.'} + +

+ + + +

+ + {"You're the one who has to watch them all, spot the pattern, and figure out the fix – "} + the tool just hands you the footage + {'.'} + +

+
+ ) +} + +export default OldWaySection diff --git a/src/components/ReplayVision/PlaceholderSection.tsx b/src/components/ReplayVision/PlaceholderSection.tsx new file mode 100644 index 000000000000..b4c491e33753 --- /dev/null +++ b/src/components/ReplayVision/PlaceholderSection.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' + +interface PlaceholderProps extends SectionComponentProps { + title?: string + note?: string +} + +// Generic "coming soon" section – used for pricing surfaces that don't have +// real data yet (TL;DR, plans, calculator, feature comparison). +const PlaceholderSection = ({ id, title = 'Coming soon', note = 'Content coming soon.' }: PlaceholderProps) => ( +
+

{title}

+

{note}

+
+) + +export default PlaceholderSection diff --git a/src/components/ReplayVision/PostHogWaySection.tsx b/src/components/ReplayVision/PostHogWaySection.tsx new file mode 100644 index 000000000000..5b1c868245ce --- /dev/null +++ b/src/components/ReplayVision/PostHogWaySection.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react' +import { IconEye, IconGraph, IconBell, IconPullRequest, IconCheckCircle } from '@posthog/icons' +import Logo from 'components/Logo' +import { ChoppyReveal } from 'components/Code/ChoppyReveal' +import { RoughAnnotation } from 'components/Code/RoughAnnotation' +import { IconPop } from 'components/Code/IconPop' +import { FlowDiagram, type FlowStep } from 'components/Code/FlowDiagram' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' +import { SectionLabel, KeyBadge } from './sectionHelpers' + +// The Replay Vision loop: the machine does the watching, diagnosing, and +// patching – you only step in to review and merge. +const steps: FlowStep[] = [ + { label: 'Replay Vision', description: 'watches the sessions', icon: IconEye, actor: 'Machine' }, + { label: 'Observation', description: 'what happened, as data', icon: IconGraph, actor: 'Machine' }, + { label: 'Signal', description: 'raised to your Inbox', icon: IconBell, actor: 'Machine' }, + { label: 'Agent', description: 'opens the PR', icon: IconPullRequest, actor: 'Machine' }, + { label: 'You', description: 'review & merge', icon: IconCheckCircle, actor: 'Human' }, +] + +const PostHogWaySection = ({ id }: SectionComponentProps) => { + const [p1Done, setP1Done] = useState(false) + + return ( +
+ + + + The{' '} + + + {' '} + PostHog way + + +

+ setP1Done(true)}> + Replay Vision + {' watches your session recordings for you. Describe what to look for once, and a scanner reads '} + {'every matching session, video and events, and turns what it sees into '} + + structured observations + + {' you can query, chart, and alert on.'} + +

+ + + +

+ + {"The friction you'd never have watched becomes a signal in your "} + Inbox + {' – where an agent picks it up and '} + + opens the pull request + + {'. You just hit '} + + Merge + + {'.'} + +

+
+ ) +} + +export default PostHogWaySection diff --git a/src/components/ReplayVision/PricingFooterCTASection.tsx b/src/components/ReplayVision/PricingFooterCTASection.tsx new file mode 100644 index 000000000000..495cc2d19d97 --- /dev/null +++ b/src/components/ReplayVision/PricingFooterCTASection.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import OSButton from 'components/OSButton' +import CloudinaryImage from 'components/CloudinaryImage' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' + +// Detective hedgehog watching a wall of monitors. +const IMAGE = + 'https://res.cloudinary.com/dmukukwp6/image/upload/noir_desk_relax_surveillance_computer_63a434c398.png' as const + +// Same look as the shared PricingFooterCTA, but laid out as two columns so the +// image sits beside the copy on the right instead of overlapping it. +const PricingFooterCTASection = ({ id }: SectionComponentProps) => { + return ( +
+
+
+
+

Still here?

+

+ The hedgehog has been watching this whole time. +

+

+ It's free to start. Not "free trial" free – actually free. No card, no call, no "someone + from our team will be in touch." Just PostHog. +

+
+ + Get started – free + + + Talk to a human + +
+

No sales call. No "tailored demo." No expiry.

+
+
+ +
+
+
+
+ ) +} + +export default PricingFooterCTASection diff --git a/src/components/ReplayVision/WorksWithSection.tsx b/src/components/ReplayVision/WorksWithSection.tsx new file mode 100644 index 000000000000..3dfb78bc4e78 --- /dev/null +++ b/src/components/ReplayVision/WorksWithSection.tsx @@ -0,0 +1,70 @@ +import React from 'react' +import { IconRewindPlay, IconGraph, IconDashboard } from '@posthog/icons' +import Link from 'components/Link' +import { InlineCode } from 'components/Products/ReaderViewProduct/helpers' +import type { SectionComponentProps } from 'components/Products/ReaderViewProduct/types' + +interface WorksWithItem { + name: string + to: string + Icon: React.ComponentType<{ className?: string }> + color: string + description: React.ReactNode +} + +const items: WorksWithItem[] = [ + { + name: 'Session Replay', + to: '/session-replay', + Icon: IconRewindPlay, + color: 'text-yellow', + description: + "The recordings your scanners watch; point Replay Vision at any set of sessions you're already capturing.", + }, + { + name: 'Product Analytics', + to: '/product-analytics', + Icon: IconGraph, + color: 'text-blue', + description: ( + <> + Every observation lands as a $recording_observed event, so you can trend and + break down what scanners find like any other insight. + + ), + }, + { + name: 'Dashboards & Alerts', + to: '/dashboards', + Icon: IconDashboard, + color: 'text-salmon', + description: + "Put frustration scores or dead-end counts on a dashboard, and get alerted when a scanner's output crosses a threshold.", + }, +] + +const WorksWithSection = ({ id }: SectionComponentProps) => { + return ( +
+

Works with other PostHog tools

+

+ Use Replay Vision with these other PostHog apps to get more out of every session. +

+
    + {items.map(({ name, to, Icon, color, description }) => ( +
  • + + + + +

    {name}

    + +

    {description}

    +
  • + ))} +
+
+ ) +} + +export default WorksWithSection diff --git a/src/components/ReplayVision/sectionHelpers.tsx b/src/components/ReplayVision/sectionHelpers.tsx new file mode 100644 index 000000000000..0c5cd43788ab --- /dev/null +++ b/src/components/ReplayVision/sectionHelpers.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { IconPop } from 'components/Code/IconPop' + +// Shared presentational helpers for the Replay Vision narrative sections +// ("The old way", "The PostHog way"). Mirrors the inline helpers used on the +// PostHog Code marketing page. + +export function SectionLabel({ children }: { children: React.ReactNode }) { + return

{children}

+} + +// Inline icon that sits in the text flow (e.g. the tombstone / logomark in a heading). +export function InlineIcon({ + icon: Icon, + children, + className = '', +}: { + icon: React.ComponentType<{ className?: string }> + children?: React.ReactNode + className?: string +}) { + return ( + + + + + {children} + + ) +} + +// Keyboard-key / badge styling (e.g. the "▶ Play" or "Merge ↵" chip). +export function KeyBadge({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/src/hooks/competitorData/contentsquare.tsx b/src/hooks/competitorData/contentsquare.tsx index 587471c9de35..c15c5c49621b 100644 --- a/src/hooks/competitorData/contentsquare.tsx +++ b/src/hooks/competitorData/contentsquare.tsx @@ -7,6 +7,37 @@ export const contentsquare = { icon: '/images/competitors/contentsquare.svg', }, products: { + replay_vision: { + available: true, + features: { + point_scanner: '100 per run', + configurable_types: 'Fixed jobs', + custom_prompt: 'Chat only', + yes_no_monitors: false, + classify_tag: false, + friction_score: '1–100', + theme_summary: '100 per run', + nl_search: 'Chat only', + scheduled_runs: true, + sampling_controls: false, + deep_link_citations: true, + mobile_replay_ai: true, + findings_events: false, + insights_dashboards: false, + feed_experiments: false, + proactive_alerts: true, + mcp_access: true, + rest_api: false, + self_driving: false, + share_recordings: true, + embed_recordings: false, + export_recordings: false, + flag_interlinking: false, + product_analytics_platform: true, + pii_redaction: true, + ai_pricing: 'By tier', + }, + }, product_analytics: { available: true, features: { diff --git a/src/hooks/competitorData/datadog.tsx b/src/hooks/competitorData/datadog.tsx index 18d5b17705a0..0f58f6e2123e 100644 --- a/src/hooks/competitorData/datadog.tsx +++ b/src/hooks/competitorData/datadog.tsx @@ -5,6 +5,37 @@ export const datadog = { icon: '/images/competitors/datadog.svg', }, products: { + replay_vision: { + available: true, + features: { + point_scanner: false, + configurable_types: 'Fixed job', + custom_prompt: false, + yes_no_monitors: false, + classify_tag: false, + friction_score: 'Signals only', + theme_summary: false, + nl_search: 'RUM data', + scheduled_runs: false, + sampling_controls: false, + deep_link_citations: true, + mobile_replay_ai: false, + findings_events: false, + insights_dashboards: false, + feed_experiments: false, + proactive_alerts: 'RUM only', + mcp_access: 'Platform', + rest_api: false, + self_driving: false, + share_recordings: true, + embed_recordings: true, + export_recordings: false, + flag_interlinking: true, + product_analytics_platform: true, + pii_redaction: true, + ai_pricing: 'Per 1k sessions', + }, + }, error_tracking: { available: true, features: { diff --git a/src/hooks/competitorData/fullstory.tsx b/src/hooks/competitorData/fullstory.tsx index 48a4854a2c45..408bf9d62feb 100644 --- a/src/hooks/competitorData/fullstory.tsx +++ b/src/hooks/competitorData/fullstory.tsx @@ -6,6 +6,37 @@ export const fullstory = { comparisonArticle: '/blog/posthog-vs-fullstory', }, products: { + replay_vision: { + available: true, + features: { + point_scanner: '10 per run', + configurable_types: 'Fixed jobs', + custom_prompt: true, + yes_no_monitors: false, + classify_tag: 'Sentiment', + friction_score: true, + theme_summary: '10 per run', + nl_search: true, + scheduled_runs: true, + sampling_controls: false, + deep_link_citations: true, + mobile_replay_ai: true, + findings_events: 'Via API', + insights_dashboards: false, + feed_experiments: false, + proactive_alerts: true, + mcp_access: true, + rest_api: true, + self_driving: false, + share_recordings: true, + embed_recordings: false, + export_recordings: false, + flag_interlinking: false, + product_analytics_platform: true, + pii_redaction: true, + ai_pricing: 'Tier + add-on', + }, + }, heatmaps: { available: true, features: { diff --git a/src/hooks/competitorData/mixpanel.tsx b/src/hooks/competitorData/mixpanel.tsx index 50ebe157e4f1..c87e1bc66e14 100644 --- a/src/hooks/competitorData/mixpanel.tsx +++ b/src/hooks/competitorData/mixpanel.tsx @@ -7,6 +7,37 @@ export const mixpanel = { }, available: true, products: { + replay_vision: { + available: true, + features: { + point_scanner: 'Playlist', + configurable_types: 'Fixed job', + custom_prompt: false, + yes_no_monitors: false, + classify_tag: false, + friction_score: 'Signals, web', + theme_summary: 'Beta', + nl_search: false, + scheduled_runs: false, + sampling_controls: 'Capture only', + deep_link_citations: true, + mobile_replay_ai: false, + findings_events: false, + insights_dashboards: false, + feed_experiments: false, + proactive_alerts: false, + mcp_access: 'Analytics only', + rest_api: false, + self_driving: false, + share_recordings: true, + embed_recordings: false, + export_recordings: false, + flag_interlinking: false, + product_analytics_platform: true, + pii_redaction: true, + ai_pricing: 'Metered + add-on', + }, + }, product_analytics: { available: true, pricing: { diff --git a/src/hooks/competitorData/posthog.tsx b/src/hooks/competitorData/posthog.tsx index 03d981c89571..7932af236251 100644 --- a/src/hooks/competitorData/posthog.tsx +++ b/src/hooks/competitorData/posthog.tsx @@ -5,6 +5,37 @@ export const posthog = { icon: '/images/logo.svg', }, products: { + replay_vision: { + available: true, + features: { + point_scanner: true, + configurable_types: 'Monitor, classifier, scorer, summarizer', + custom_prompt: true, + yes_no_monitors: true, + classify_tag: true, + friction_score: true, + theme_summary: true, + nl_search: true, + scheduled_runs: true, + sampling_controls: true, + deep_link_citations: true, + mobile_replay_ai: false, + findings_events: true, + insights_dashboards: true, + feed_experiments: true, + proactive_alerts: true, + mcp_access: true, + rest_api: true, + self_driving: true, + share_recordings: true, + embed_recordings: true, + export_recordings: true, + flag_interlinking: true, + product_analytics_platform: true, + pii_redaction: true, + ai_pricing: 'Usage, beta', + }, + }, heatmaps: { available: true, features: { @@ -509,82 +540,82 @@ export const posthog = { built_in_analytics: true, }, }, -ai_observability: { - available: true, - features: { - alerting: true, - cost_tracking: true, - generation_tracking: true, - latency_tracking: true, - prompt_evaluations: true, - prompt_playground: true, - token_tracking: true, - trace_visualization: true, - error_tracking: true, - clustering: true, - system_prompts: true, - trace_summarization: true, - llm_translation: true, - sentiment_classification: 'Beta', - privacy_mode: true, - agent_tracing: 'Basic', - prompt_management: 'Beta', - evaluation_datasets: false, - human_annotation: false, - session_replay: true, - product_analytics: true, - ai_gateway_proxy: false, - }, - tracing: { - features: { - hierarchical_traces: true, - custom_spans: true, - tool_call_tracking: true, - rag_retrieval_tracking: true, - session_grouping: true, - opentelemetry_support: true, - async_ingestion: true, - multi_model_support: true, - session_replay_link: true, - user_profile_context: true, - sql_queries_on_traces: true, - trace_explorer_ui: 'Basic', - }, - }, - prompt_management: { - features: { - prompt_versioning: 'Beta', - template_variables: 'Beta', - prompt_deployment_api: 'Beta', - version_comparison: 'Beta', - prompt_labels: false, - prompt_playground: true, - composable_prompts: false, - mcp_server_for_prompts: 'Beta', - ab_test_prompt_versions: 'Beta', - }, - }, - evaluations: { - features: { - llm_as_a_judge: true, - code_evaluators: true, - annotation_queues: false, - datasets: false, - experiment_runs: false, - ab_experiments_on_product_metrics: true, - }, - }, - costs: { - features: { - token_counting: true, - cost_calculation: true, - cost_by_model: true, - cost_trends: true, - cost_by_user: true, - cost_by_feature: true, - cost_by_cohort: true, - }, - }, + ai_observability: { + available: true, + features: { + alerting: true, + cost_tracking: true, + generation_tracking: true, + latency_tracking: true, + prompt_evaluations: true, + prompt_playground: true, + token_tracking: true, + trace_visualization: true, + error_tracking: true, + clustering: true, + system_prompts: true, + trace_summarization: true, + llm_translation: true, + sentiment_classification: 'Beta', + privacy_mode: true, + agent_tracing: 'Basic', + prompt_management: 'Beta', + evaluation_datasets: false, + human_annotation: false, + session_replay: true, + product_analytics: true, + ai_gateway_proxy: false, + }, + tracing: { + features: { + hierarchical_traces: true, + custom_spans: true, + tool_call_tracking: true, + rag_retrieval_tracking: true, + session_grouping: true, + opentelemetry_support: true, + async_ingestion: true, + multi_model_support: true, + session_replay_link: true, + user_profile_context: true, + sql_queries_on_traces: true, + trace_explorer_ui: 'Basic', + }, + }, + prompt_management: { + features: { + prompt_versioning: 'Beta', + template_variables: 'Beta', + prompt_deployment_api: 'Beta', + version_comparison: 'Beta', + prompt_labels: false, + prompt_playground: true, + composable_prompts: false, + mcp_server_for_prompts: 'Beta', + ab_test_prompt_versions: 'Beta', + }, + }, + evaluations: { + features: { + llm_as_a_judge: true, + code_evaluators: true, + annotation_queues: false, + datasets: false, + experiment_runs: false, + ab_experiments_on_product_metrics: true, + }, + }, + costs: { + features: { + token_counting: true, + cost_calculation: true, + cost_by_model: true, + cost_trends: true, + cost_by_user: true, + cost_by_feature: true, + cost_by_cohort: true, + }, + }, }, workflows: { available: true, @@ -803,4 +834,4 @@ ai_observability: { pricing: { model: 'Usage-based', }, - } \ No newline at end of file +} diff --git a/src/hooks/productData/replay_vision.tsx b/src/hooks/productData/replay_vision.tsx new file mode 100644 index 000000000000..0e0f96d96db8 --- /dev/null +++ b/src/hooks/productData/replay_vision.tsx @@ -0,0 +1,317 @@ +import React from 'react' +import { + IconLlmPromptEvaluation, + IconEye, + IconWarning, + IconSparkles, + IconPeople, + IconCursorClick, + IconList, + IconChat, + IconConfetti, + IconMap, + IconNewspaper, + IconMessage, + IconShieldPeople, + IconCode, + IconRocket, + IconInfo, + IconCheckCircle, + IconPieChart, + IconGraph, +} from '@posthog/icons' +import OldWaySection from 'components/ReplayVision/OldWaySection' +import PostHogWaySection from 'components/ReplayVision/PostHogWaySection' +import HowToUseSection from 'components/ReplayVision/HowToUseSection' +import AIPromptsSection from 'components/ReplayVision/AIPromptsSection' +import WorksWithSection from 'components/ReplayVision/WorksWithSection' +import PlaceholderSection from 'components/ReplayVision/PlaceholderSection' +import PricingFooterCTASection from 'components/ReplayVision/PricingFooterCTASection' +import { sessionReplay } from './session_replay' +import { topFeatures } from './replay_vision/slides' + +// Feature-comparison rows. Header rows group the table; feature rows carry the +// row label and pull each competitor's value from `products.replay_vision.features` +// in the competitor data files (competitorData/*). +const feature = (key: string, label: string, description: string) => ({ + type: 'feature' as const, + product: 'replay_vision', + feature: key, + label, + description, +}) + +const featureComparisonRows = [ + { type: 'header' as const, label: 'AI scanners' }, + feature( + 'point_scanner', + 'Point a scanner at a filtered recording set', + 'Run AI analysis across a whole slice of sessions, not one recording at a time.' + ), + feature( + 'configurable_types', + 'Configurable scanner types', + 'Choose how a scanner works – monitor, classifier, scorer, or summarizer – instead of a fixed job.' + ), + feature( + 'custom_prompt', + 'Custom prompt per analysis', + 'Describe exactly what to look for in your own words for each scanner.' + ), + feature( + 'yes_no_monitors', + 'Yes/no monitors (did this happen?)', + 'Get a simple did-this-happen verdict on every session a scanner watches.' + ), + feature( + 'classify_tag', + 'Classify / tag sessions', + 'Automatically bucket sessions by intent, outcome, or any label you define.' + ), + feature( + 'friction_score', + 'Numeric friction / struggle score', + 'Rate how much friction each session hit on a scale you control.' + ), + feature( + 'theme_summary', + 'Cross-session theme summary', + 'Roll many sessions up into the recurring themes and patterns across them.' + ), + feature( + 'nl_search', + 'Natural-language search over sessions', + 'Find the recordings you need by describing them in plain language.' + ), + feature( + 'scheduled_runs', + 'Scheduled / continuous runs', + 'Scanners keep running on new sessions automatically, not just on demand.' + ), + feature( + 'sampling_controls', + 'Sampling / coverage controls', + 'Dial how much matching traffic gets scanned so cost stays predictable.' + ), + feature( + 'deep_link_citations', + 'Deep-link citations to the exact moment', + 'Every finding links straight to the timestamp in the recording so you can verify it.' + ), + feature( + 'mobile_replay_ai', + 'Mobile replay AI', + 'Run the same AI analysis over native iOS and Android session recordings.' + ), + { type: 'header' as const, label: 'Results as data' }, + feature( + 'findings_events', + 'Findings become queryable events', + 'Each observation is emitted as an event you can query alongside the rest of your data.' + ), + feature( + 'insights_dashboards', + 'Build insights / dashboards on AI output', + 'Chart and monitor what scanners find like any other product metric.' + ), + feature( + 'feed_experiments', + 'Feed experiments / cohorts', + 'Use scanner output to build cohorts and measure it in experiments.' + ), + feature( + 'proactive_alerts', + 'Proactive alerts / anomaly detection', + "Get notified when a scanner's output spikes or crosses a threshold." + ), + { type: 'header' as const, label: 'Access and agents' }, + feature( + 'mcp_access', + 'MCP / agent access', + 'Author, run, and read scanners from any MCP client via the PostHog MCP.' + ), + feature('rest_api', 'REST API for AI output', 'Pull scanner observations programmatically over a REST API.'), + feature( + 'self_driving', + 'Feeds the self-driving loop', + 'Findings raise signals that agents can research and turn into a pull request.' + ), + { type: 'header' as const, label: 'Recordings and platform' }, + feature('share_recordings', 'Share recordings', 'Share a link to any recording with your team.'), + feature('embed_recordings', 'Embed recordings', 'Embed recordings in docs, dashboards, or other tools.'), + feature('export_recordings', 'Export recordings', 'Export recordings out of the platform when you need to.'), + feature( + 'flag_interlinking', + 'Feature-flag interlinking', + 'Jump between recordings and the feature flags and experiments they hit.' + ), + feature( + 'product_analytics_platform', + 'Product analytics in same platform', + 'Analytics, replays, and AI findings all live in one product, not stitched together.' + ), + feature('pii_redaction', 'PII redaction / masking', 'Mask sensitive content before it is ever recorded.'), + { type: 'header' as const, label: 'Pricing' }, + feature('ai_pricing', 'AI pricing model', 'How the AI analysis is billed.'), +] + +export const replayVision = { + Icon: IconLlmPromptEvaluation, + name: 'Replay Vision', + handle: 'replay_vision', + slug: 'replay-vision', + // Built by the same team as Session Replay, so the team-driven sections + // (roadmap, changelog, questions, team) pull from the same sources. + teamSlug: 'replay', + forumTopicId: 377, + color: 'yellow', + colorSecondary: '[#B56C00]', + category: 'product_engineering', + status: 'beta', + shortDescription: 'Let AI watch your session replays for you', + seo: { + title: 'Replay Vision - PostHog', + description: + 'Replay Vision reads through a filtered set of replays, tells you in plain language what went wrong, and opens a pull request with the fix. The problem surfaces itself, and so does the patch. You just hit merge.', + }, + /** + * Sections rendered on the Product surface (`/replay-vision`). Each entry + * resolves to a section template via `templateRegistry[item.template ?? item.slug]`. + * Only the hero (`overview`) is shipped for now – more sections get added here. + */ + productMenu: [ + { slug: 'overview', name: 'Overview', icon: }, + { + slug: 'old-way', + name: 'The old way', + component: OldWaySection, + icon: , + }, + { + slug: 'posthog-way', + name: 'The PostHog way', + component: PostHogWaySection, + icon: , + }, + { slug: 'use-cases', name: 'Who is it for?', icon: }, + { + slug: 'how-to-use', + name: 'How to use it', + component: HowToUseSection, + icon: , + }, + { + slug: 'top-features', + name: 'Top features', + icon: , + props: { slides: topFeatures }, + }, + { + slug: 'ai-prompts', + name: 'AI prompts', + component: AIPromptsSection, + icon: , + }, + { + slug: 'works-with', + name: 'Works with...', + component: WorksWithSection, + icon: , + }, + { slug: 'roadmap', name: 'Roadmap', icon: }, + { slug: 'changelog', name: 'Changelog', icon: }, + { slug: 'community', name: 'Questions?', icon: }, + { slug: 'team', name: 'Team', icon: }, + { slug: 'installation', name: 'Install', icon: }, + { slug: 'getting-started', name: 'Get started', icon: }, + ], + /** + * Sections rendered on the Pricing surface (`/replay-vision/pricing`). + * Replay Vision isn't priced yet, so the pricing-specific sections (TL;DR, + * plans, calculator, feature comparison) are placeholders. Only the + * "Replay Vision vs..." comparison is built out. + */ + pricingMenu: [ + { + slug: 'pricing-tldr', + name: 'TL;DR', + component: PlaceholderSection, + props: { title: 'Pricing TL;DR' }, + icon: , + }, + { + slug: 'plans', + name: 'Plans', + component: PlaceholderSection, + props: { title: 'Plans' }, + icon: , + }, + { + slug: 'calculator', + name: 'Pricing calculator', + component: PlaceholderSection, + props: { title: 'Pricing calculator' }, + icon: , + }, + { slug: 'comparison-summary', name: 'PostHog vs...', icon: }, + { slug: 'feature-comparison', name: 'Feature comparison', icon: }, + { slug: 'pricing-cta', name: 'Get started', hideFromNav: true, component: PricingFooterCTASection }, + ], + overview: { + title: 'Your product, watching itself', + description: + 'Replay Vision reads through a filtered set of replays, tells you in plain language what went wrong, and opens a pull request with the fix. The problem surfaces itself, and so does the patch. You just hit merge.', + textColor: 'text-black', // tw + }, + screenshots: { + home: { + src: 'https://res.cloudinary.com/dmukukwp6/image/upload/Group_4_1_33ba4bbdb7.png', + srcDark: 'https://res.cloudinary.com/dmukukwp6/image/upload/Group_6_c5824625a7.png', + alt: 'Replay Vision scanner observations', + }, + }, + useCases: { + intro: 'Replay Vision is used across teams depending on your role.', + rows: [ + ['Product Engineers', "Scan sessions in bulk for the failure you can't reproduce locally"], + ['PMs & Designers', 'Score friction and spot dead ends across a release without watching a replay'], + ['Growth', 'Find where users bleed out of onboarding and signup funnels'], + ['Support & UX Research', 'Classify what users were actually trying to do, at scale'], + ['Founders', 'Skim a one-line summary of every session instead of spending hours watching them'], + ], + }, + // Same install surface as Session Replay – pulls from the same source. + installation: sessionReplay.installation, + comparison: { + summary: { + them: [ + { title: 'You want self-hosting or more strict data residency' }, + { title: 'You have strong security requirements that need more robust PII redaction' }, + { title: 'You want a dedicated UX-research tool with targeted clip capture and studies' }, + { title: "You're not on PostHog and don't want to move session replay here" }, + ], + us: [ + { + title: 'You want to ask any question of any slice of recordings on a schedule – not pick from a fixed menu of AI jobs', + }, + { + title: 'Findings land as queryable observations next to your analytics, funnels, flags, experiments, and errors', + }, + { + title: "Agents can search and act on what's in your replays via the MCP – the context that powers self-driving", + }, + { title: "You'd rather keep sessions on PostHog than pay a second tool to scrape them out" }, + ], + }, + companies: [ + { name: 'FullStory', key: 'fullstory', link: '/blog/posthog-vs-fullstory' }, + { name: 'Contentsquare', key: 'contentsquare' }, + { name: 'Datadog', key: 'datadog' }, + { name: 'Mixpanel', key: 'mixpanel', link: '/blog/posthog-vs-mixpanel' }, + { name: 'PostHog', key: 'posthog' }, + ], + rows: featureComparisonRows, + // Feature pages don't render platform sections; our rows are already scoped. + excluded_sections: ['platform'], + }, +} diff --git a/src/hooks/productData/replay_vision/slides.tsx b/src/hooks/productData/replay_vision/slides.tsx new file mode 100644 index 000000000000..84160fad101e --- /dev/null +++ b/src/hooks/productData/replay_vision/slides.tsx @@ -0,0 +1,433 @@ +import React from 'react' +import { + IconCode, + IconWarning, + IconDocument, + IconMagic, + IconTrending, + IconCheckCircle, + IconTerminal, + IconBrowser, + IconGraph, + IconBell, + IconEye, +} from '@posthog/icons' +import type { CarouselSlide } from 'components/Products/ReaderViewProduct/types' +import { LabeledList, InlineCode } from 'components/Products/ReaderViewProduct/helpers' +import PlatformInstall, { wizardInstallSchema } from 'components/PlatformInstall' +import CloudinaryImage from 'components/CloudinaryImage' +import Glow from 'components/Glow' + +// The "How do I use it?" carousel – four ways to put Replay Vision to work. +export const applications: CarouselSlide[] = [ + { + slug: 'editor-mcp', + label: 'Editor / MCP', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-purple', + layout: 'stack', + heading: 'Author, run, and read scanners without leaving your editor', + description: ( + <> + +

+ Your AI coding agent can call Replay Vision directly through the PostHog MCP – in Cursor, Claude + Code, Codex, VS Code, or any MCP client. +

+
+ +
+
+ +
+ + ), + }, + { + slug: 'in-the-app', + label: 'In the app', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-blue', + layout: 'stack', + heading: 'Describe it once, let it run', + description: ( + <> +

+ Build a scanner in the wizard – prompt, scanner type, recording filters, and sampling rate – and it + runs continuously on every matching session. +

+
+ +
+ + ), + image: { + src: 'https://res.cloudinary.com/dmukukwp6/image/upload/Clean_Shot_2026_07_22_at_16_53_55_2x_042bd6369d.png', + srcDark: + 'https://res.cloudinary.com/dmukukwp6/image/upload/Clean_Shot_2026_07_22_at_16_58_56_2x_292863671d.png', + }, + }, + { + slug: 'query-alert', + label: 'Query & alert', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-green', + layout: 'stack', + heading: 'Sessions become data you can work with', + description: ( + <> +

+ Every observation is emitted as a $recording_observed event, so what Replay + Vision sees lives right next to the rest of your product data. +

+
+ +
+ + ), + image: { + src: 'https://res.cloudinary.com/dmukukwp6/image/upload/Clean_Shot_2026_07_22_at_16_56_27_2x_1fedaa73d8.png', + srcDark: + 'https://res.cloudinary.com/dmukukwp6/image/upload/Clean_Shot_2026_07_22_at_16_57_50_2x_720995e1f8.png', + }, + }, + { + slug: 'inbox-self-driving', + label: 'Self-driving', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-yellow', + layout: 'stack', + heading: 'From observation to open PR, while you sleep', + description: ( + <> +

+ An observation doesn't just sit in a dashboard – it can raise a signal into your + Inbox, where signals are clustered into a prioritized report, and the actionable + ones come back as a pull request with the fix attached. +

+
+ + Nothing ships on autopilot. You review, hit Merge ↵, + and the loop measures whether the metric moved. + + ), + }, + ]} + /> +
+
+ +
+ + ), + image: { + src: 'https://res.cloudinary.com/dmukukwp6/image/upload/Clean_Shot_2026_07_22_at_17_03_10_2x_1_4c236ec771.png', + srcDark: + 'https://res.cloudinary.com/dmukukwp6/image/upload/Clean_Shot_2026_07_22_at_17_02_08_2x_1_eb56d67e29.png', + }, + }, +] + +// The six built-in scanner types shown in the "What it looks for" tab. +const scannerCards: { + Icon: React.ComponentType<{ className?: string }> + color: string + title: string + type?: string + description: string +}[] = [ + { + Icon: IconCode, + color: 'text-purple', + title: 'Create from scratch', + description: 'Build a fully custom scanner – pick a type and write your own prompt and config.', + }, + { + Icon: IconWarning, + color: 'text-red', + title: 'Dead ends', + type: 'Monitor', + description: 'Catch the moment someone hits a wall: scrolling, hovering with no CTA, then rage-quitting.', + }, + { + Icon: IconDocument, + color: 'text-green', + title: 'Session summary', + type: 'Summarizer', + description: "The TL;DR of the session, so you don't sit through 14 minutes of someone scrolling.", + }, + { + Icon: IconMagic, + color: 'text-blue', + title: 'User intent', + type: 'Classifier', + description: 'Classify the session by what the user appeared to be trying to do.', + }, + { + Icon: IconTrending, + color: 'text-yellow', + title: 'Frustration score', + type: 'Scorer', + description: 'Rate how much friction a page caused, on a scale you define.', + }, + { + Icon: IconCheckCircle, + color: 'text-teal', + title: 'Session outcome', + type: 'Classifier', + description: 'Tag what actually happened – task completed, abandoned, errored, and so on.', + }, +] + +// Top features for Replay Vision – how scanners look, get configured, and run. +export const topFeatures: CarouselSlide[] = [ + { + slug: 'what-it-looks-for', + label: 'What it looks for', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-purple', + layout: 'stack', + heading: 'What it looks for', + description: ( + <> +

+ Replay Vision runs scanners – AI probes you configure and point at your sessions. + Pick a type, describe what to look for, and it produces structured output on each session it scans. + Start from a built-in template or from scratch. +

+
+
+ {scannerCards.map(({ Icon, color, title, type, description }) => ( +
+
+ +

+ {title} + {type && ( + ({type}) + )} +

+
+

{description}

+
+ ))} +
+
+

+ Every observation comes with a confidence score and citations that link straight to the exact moment + in the recording, so you can verify it in one click. +

+ + ), + }, + { + slug: 'how-it-runs', + label: 'How it runs', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-green', + layout: 'stack', + heading: 'How it runs', + description: ( + <> +

+ Once enabled, a scanner works in the background – and you can also run it by hand whenever you're + investigating. +

+
+ + Trigger a scan via the vision-scanners-scan-session MCP + tool without leaving your IDE. + + ), + }, + { + label: 'Never double-counts', + description: + 'Each scanner observes a given session only once, so re-sweeping never duplicates work or cost.', + }, + { + label: "Skips what it can't judge", + description: + 'Too-short, idle, or recording-less sessions come back "ineligible" – and don\'t count against your quota.', + }, + ]} + /> +
+ + ), + }, + { + slug: 'what-you-get-back', + label: 'What you get back', + icon: , + color: 'bg-light dark:bg-dark', + activeText: 'text-primary', + progressBar: 'bg-yellow', + layout: 'stack', + heading: 'What you get back', + description: ( + <> +

+ Each scan produces a structured observation – and it doesn't just sit in a table. +

+
+ + Observations land as $recording_observed events, so you + can chart, break down, and alert on them next to the rest of your data. + + ), + }, + { + label: 'Hand off to Responder agents', + description: + 'Flip a toggle and a scanner also flags concrete product issues as signals to the self-driving Inbox, where agents research them and can open a PR.', + }, + ]} + /> +
+ + ), + }, +] diff --git a/src/hooks/useProduct.ts b/src/hooks/useProduct.ts index 714ecd1d1c49..08f85dddd78d 100644 --- a/src/hooks/useProduct.ts +++ b/src/hooks/useProduct.ts @@ -34,6 +34,7 @@ import { import useProducts from './useProducts' import { mcpAnalytics } from './productData/mcp_analytics' import { traces } from './productData/traces' +import { replayVision } from './productData/replay_vision' const dedupe = (products) => { const deduped = {} @@ -60,6 +61,7 @@ export default function useProduct({ handle }: { handle?: string } = {}) { // slug: 'product-analytics', // }, traces, + replayVision, { name: 'User interviews', Icon: IconThoughtBubble, diff --git a/src/pages/replay-vision.tsx b/src/pages/replay-vision.tsx deleted file mode 100644 index de09019f5c1a..000000000000 --- a/src/pages/replay-vision.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import React, { useState } from 'react' -import SEO from 'components/seo' -import Editor from 'components/Editor' -import { - IconCode, - IconWarning, - IconDocument, - IconCursorClick, - IconSort, - IconTrending, - IconCheckCircle, - IconLlmPromptEvaluation, -} from '@posthog/icons' -import OSButton from 'components/OSButton' -import { WaitlistForm } from 'components/WaitlistForm' -import CloudinaryImage from 'components/CloudinaryImage' - -const SURVEY_ID = '019e82bb-e78f-0000-7141-addb508840d4' - -const scannerCards = [ - { - icon: IconCode, - color: 'text-purple', - bgColor: 'bg-purple/10', - title: 'Create from scratch', - description: 'Build a fully custom scanner with your own prompt and configuration.', - }, - { - icon: IconWarning, - color: 'text-red', - bgColor: 'bg-red/10', - title: 'Dead ends', - description: 'Catch the moment someone hits a wall, stares at it, and rage-quits.', - }, - { - icon: IconDocument, - color: 'text-seagreen', - bgColor: 'bg-green/10', - title: 'Session summary', - description: "The TL;DR of the session, so you don't have to sit through 14 minutes of someone scrolling.", - }, - { - icon: IconCursorClick, - color: 'text-blue', - bgColor: 'bg-blue/10', - title: 'User intent', - description: 'Classify the session by what the user appeared to be trying to do.', - }, - { - icon: IconTrending, - color: 'text-yellow', - bgColor: 'bg-yellow/10', - title: 'Frustration score', - description: 'Rate how mad the page made someone, from mild sigh to keyboard-smash.', - }, - { - icon: IconCheckCircle, - color: 'text-teal', - bgColor: 'bg-teal/10', - title: 'Session outcome', - description: 'Tag each session with what actually happened - task completed, abandoned, errored, etc.', - }, -] - -const useCaseCards = [ - { - icon: IconWarning, - color: 'text-red', - title: 'Find the bugs that hurt', - description: 'Surface errors that were actually visible to users and blocked them from finishing a task.', - }, - { - icon: IconTrending, - color: 'text-yellow', - title: 'Stop watching sessions at random', - description: - "The scorer ranks your sessions according to relevance, so you won't waste time watching sessions that don't matter.", - }, - { - icon: IconCursorClick, - color: 'text-blue', - title: 'Spot patterns without losing your afternoon', - description: - 'Sessions get tagged automatically, so finding out where people get stuck on mobile checkout is just a search away.', - }, -] - -function HeroSection() { - const [showForm, setShowForm] = useState(false) - - return ( -
-
-
- -
-
- - Replay Vision -
-

- The fast-forward button for session replay. -

-
-
-

- Point an AI scanner at your recordings, and let it watch every session for you: flagging - bugs, scoring frustration, tagging behavior, and summarizing what happened. You get the - findings, while Replay Vision does the homework. -

-
- {showForm ? ( - - ) : ( -
- setShowForm(true)}> - Join the waitlist - -
- )} -
-
- -
-
-
- ) -} - -function HowItWorks() { - return ( -
-

How it works

-

- Replay Vision runs scanners: AI agents you configure and point at your sessions. Set a trigger, pick - what you want it to look for, and it runs across every matching recording automatically. -

- -
- {scannerCards.map(({ icon: Icon, color, bgColor, title, description }) => ( -
-
- -

{title}

-
-

{description}

-
- ))} -
- -

- Every observation comes with a confidence score and links straight to the exact moment in the recording, - so you can verify it in one click. -

-
- ) -} - -function UseCases() { - return ( -
-

What you'd actually use it for

- -
    - {useCaseCards.map(({ icon: Icon, color, title, description }, i) => ( -
  • - -

    {title}

    -

    {description}

    -
  • - ))} -
-
- ) -} - -function HonestBit() { - return ( -
-

The honest bit

-

- It's early. Replay Vision is getting near a closed beta. We're rolling access out in batches so we can - actually talk to the people using it and get the quality right before we go wide. Join the list and - you'll be near the front. -

-
- -
-
- ) -} - -export default function ReplayVisionPage() { - return ( - <> - - -
-
- - -
- -
-
- -
- - - -
-
-
- - ) -} diff --git a/src/pages/replay-vision/index.tsx b/src/pages/replay-vision/index.tsx new file mode 100644 index 000000000000..c2168bd996ed --- /dev/null +++ b/src/pages/replay-vision/index.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import ProductReaderView from 'components/Products/ReaderViewProduct' + +export default function ReplayVision(): JSX.Element { + return +} diff --git a/src/pages/replay-vision/pricing.tsx b/src/pages/replay-vision/pricing.tsx new file mode 100644 index 000000000000..580eb7008dba --- /dev/null +++ b/src/pages/replay-vision/pricing.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import ProductReaderView from 'components/Products/ReaderViewProduct' + +export default function ReplayVisionPricing(): JSX.Element { + return ( + + ) +}