From 68836f437357c619995eea3fedf93f3ff050dccf Mon Sep 17 00:00:00 2001 From: Peter Kirkham Date: Wed, 27 May 2026 11:03:44 +0100 Subject: [PATCH 01/12] Sharpen upgrade copy on usage limit modal and plan page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lead with the concrete value prop ($1,000 of Claude & Codex usage / month for $200) instead of vague phrases like "higher usage limits" and "unlimited usage." Frames Pro as 20× the Free allowance in the modal and plan cards, and 5× what you pay in the upgrade confirm dialog where price is the anchor. Sidebar usage bar is intentionally left unchanged — kept percentage- based rather than introducing $ values in a constrained space. Values are inlined for now; if billing changes the $50 / $1,000 / $200 numbers, three spots need updating. A follow-up could plumb the limit through usageBucketSchema so the renderer reads it from the API. Generated-By: PostHog Code Task-Id: 7ea15f76-06cb-414a-8d45-477e97ed95e7 --- .../features/billing/components/UsageLimitModal.tsx | 10 ++++++---- .../components/sections/PlanUsageSettings.tsx | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx b/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx index 386687dc52..694742fa35 100644 --- a/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx +++ b/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx @@ -22,12 +22,14 @@ export function UsageLimitModal() { - Usage limit reached + + You're out of usage for this month + - You've reached your free plan usage limit. Upgrade to Pro for - unlimited usage. + Free includes $50 of Claude and Codex usage per month. Pro + includes $1,000 of usage for $200/month — 20× more than Free. @@ -35,7 +37,7 @@ export function UsageLimitModal() { Not now diff --git a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx index b3001f596b..99b708ee25 100644 --- a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx @@ -184,7 +184,7 @@ export function PlanUsageSettings() { price="$0" period="/mo" features={[ - "Limited usage", + "$50 of Claude & Codex usage / month", "Local and cloud execution", "All Claude and Codex models", ]} @@ -195,7 +195,7 @@ export function PlanUsageSettings() { price="$200" period="/mo" features={[ - "Higher usage limits", + "$1,000 of Claude & Codex usage / month (20× Free)", "Local and cloud execution", "All Claude and Codex models", ]} @@ -356,13 +356,15 @@ export function PlanUsageSettings() { ) : ( "Your organization" )}{" "} - will be charged $200/month using the payment method on file in - PostHog. + will be charged $200/month. Includes $1,000 of Claude and Codex API + usage per month — 5× what you pay. - Higher usage limits + + $1,000 of Claude & Codex usage / month + From b958f256e587a94e62a3834f77303eec40100d2a Mon Sep 17 00:00:00 2001 From: Peter Kirkham Date: Wed, 27 May 2026 11:15:59 +0100 Subject: [PATCH 02/12] Instrument upgrade prompts so we can measure the funnel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two events following the Object-verbed + discriminator pattern from docs/conventions.md: - `Upgrade prompt shown` with surface ∈ {usage_limit_modal, upgrade_dialog} - `Upgrade prompt clicked` with surface ∈ {usage_limit_modal, sidebar, plan_page_card, upgrade_dialog} Wired into UsageLimitModal (impression + CTA), SidebarUsageBar (CTA), and PlanUsageSettings (Pro card upgrade CTA, confirm dialog impression, and Subscribe button intent fired before upgradeToPro() so the gap to the existing `Subscription started` event captures payment/API failures). Once shipped, the funnel "saw the limit but didn't subscribe" becomes answerable: Upgrade prompt shown (surface=usage_limit_modal) → Upgrade prompt clicked (surface=usage_limit_modal) → Subscription started. Generated-By: PostHog Code Task-Id: 7ea15f76-06cb-414a-8d45-477e97ed95e7 --- .../billing/components/SidebarUsageBar.tsx | 3 +++ .../billing/components/UsageLimitModal.tsx | 14 +++++++++++ .../components/sections/PlanUsageSettings.tsx | 20 ++++++++++++++- apps/code/src/shared/types/analytics.ts | 25 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx b/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx index e2dfdcb60f..ec1f27bfb9 100644 --- a/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx +++ b/apps/code/src/renderer/features/billing/components/SidebarUsageBar.tsx @@ -4,6 +4,8 @@ import { useSettingsDialogStore } from "@features/settings/stores/settingsDialog import { useFeatureFlag } from "@hooks/useFeatureFlag"; import { Circle } from "@phosphor-icons/react"; import { BILLING_FLAG } from "@shared/constants"; +import { ANALYTICS_EVENTS } from "@shared/types/analytics"; +import { track } from "@utils/analytics"; export function SidebarUsageBar() { const billingEnabled = useFeatureFlag(BILLING_FLAG); @@ -12,6 +14,7 @@ export function SidebarUsageBar() { if (!billingEnabled) return null; const handleUpgrade = () => { + track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, { surface: "sidebar" }); useSettingsDialogStore.getState().open("plan-usage"); }; diff --git a/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx b/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx index 694742fa35..186aa59870 100644 --- a/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx +++ b/apps/code/src/renderer/features/billing/components/UsageLimitModal.tsx @@ -2,12 +2,26 @@ import { useUsageLimitStore } from "@features/billing/stores/usageLimitStore"; import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore"; import { WarningCircle } from "@phosphor-icons/react"; import { Button, Dialog, Flex, Text } from "@radix-ui/themes"; +import { ANALYTICS_EVENTS } from "@shared/types/analytics"; +import { track } from "@utils/analytics"; +import { useEffect } from "react"; export function UsageLimitModal() { const isOpen = useUsageLimitStore((s) => s.isOpen); const hide = useUsageLimitStore((s) => s.hide); + useEffect(() => { + if (isOpen) { + track(ANALYTICS_EVENTS.UPGRADE_PROMPT_SHOWN, { + surface: "usage_limit_modal", + }); + } + }, [isOpen]); + const handleUpgrade = () => { + track(ANALYTICS_EVENTS.UPGRADE_PROMPT_CLICKED, { + surface: "usage_limit_modal", + }); hide(); useSettingsDialogStore.getState().open("plan-usage"); }; diff --git a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx index 99b708ee25..fd4d3a7a88 100644 --- a/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/PlanUsageSettings.tsx @@ -24,7 +24,9 @@ import { Text, } from "@radix-ui/themes"; import { Tooltip } from "@renderer/components/ui/Tooltip"; +import { ANALYTICS_EVENTS } from "@shared/types/analytics"; import { PLAN_PRO_ALPHA } from "@shared/types/seat"; +import { track } from "@utils/analytics"; import { logger } from "@utils/logger"; import { getBillingUrl, getPostHogUrl } from "@utils/urls"; import { useEffect, useState } from "react"; @@ -86,6 +88,14 @@ export function PlanUsageSettings() { void refetchUsage(); }, [fetchSeat, refetchUsage]); + useEffect(() => { + if (showUpgradeDialog) { + track(ANALYTICS_EVENTS.UPGRADE_PROMPT_SHOWN, { + surface: "upgrade_dialog", + }); + } + }, [showUpgradeDialog]); + const formattedActiveUntil = activeUntil ? activeUntil.toLocaleDateString(undefined, { month: "short", @@ -238,7 +248,12 @@ export function PlanUsageSettings() {