From 834ddc226f28cf728b2f5b0fe9bf363443ed7ee3 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 1 Aug 2026 01:51:22 -0700 Subject: [PATCH 1/6] Simplify annual pricing labels --- web/app/[locale]/dashboard/billing/page.tsx | 27 --------------------- web/app/[locale]/pricing/page.tsx | 26 -------------------- web/app/app-pricing/page.tsx | 26 -------------------- web/messages/en.json | 5 ++-- web/messages/ja.json | 5 ++-- web/tests/app-pricing-page.test.tsx | 8 +++--- web/tests/dashboard-billing-page.test.tsx | 8 +++--- web/tests/pricing-page.test.tsx | 8 +++--- 8 files changed, 16 insertions(+), 97 deletions(-) diff --git a/web/app/[locale]/dashboard/billing/page.tsx b/web/app/[locale]/dashboard/billing/page.tsx index a0460b4f0fd8..0246c35f4db3 100644 --- a/web/app/[locale]/dashboard/billing/page.tsx +++ b/web/app/[locale]/dashboard/billing/page.tsx @@ -12,7 +12,6 @@ import { localizedVaultPath, vaultSignInHref } from "@/app/lib/vault-auth"; import { FeatureList, PlanCard, - PrimaryLink, visibleProFeatures, } from "@/app/components/pricing-shared"; import { @@ -291,19 +290,6 @@ function FreePlanUpsell({ annual={pricingT("perMonthBilledYearly")} /> } - priceDetail={ - - {pricingT("annualPriceDetail", { - amount: PRO_PRICING_USD.year.billedAmount, - discount: PRO_PRICING_USD.year.discountPercent, - })} -

- } - /> - } > } - priceDetail={ - - {pricingT("annualPriceDetail", { - amount: TEAM_PRICING_USD.year.billedAmount, - discount: TEAM_PRICING_USD.year.discountPercent, - })} -

- } - /> - } > } - priceDetail={ - - {t("annualPriceDetail", { - amount: PRO_PRICING_USD.year.billedAmount, - discount: PRO_PRICING_USD.year.discountPercent, - })} -

- } - /> - } badge={ snapshot.isPro ? ( {t("currentPlan")} @@ -242,19 +229,6 @@ export default async function PricingPage({ annual={t("perUserMonthBilledYearly")} /> } - priceDetail={ - - {t("annualPriceDetail", { - amount: TEAM_PRICING_USD.year.billedAmount, - discount: TEAM_PRICING_USD.year.discountPercent, - })} -

- } - /> - } > } - priceDetail={ - - {pricingMessage(pricing.annualPriceDetail, { - amount: PRO_PRICING_USD.year.billedAmount, - discount: PRO_PRICING_USD.year.discountPercent, - })} -

- } - /> - } badge={ snapshot.isPro ? ( {pricing.currentPlan} @@ -225,19 +212,6 @@ export default async function AppPricingPage({ annual={pricing.perUserMonthBilledYearly} /> } - priceDetail={ - - {pricingMessage(pricing.annualPriceDetail, { - amount: TEAM_PRICING_USD.year.billedAmount, - discount: TEAM_PRICING_USD.year.discountPercent, - })} -

- } - /> - } > {appStorePaymentGated ? ( {pricing.billingUnavailable} diff --git a/web/messages/en.json b/web/messages/en.json index a07e493bb56c..e44c9f5e6482 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -404,14 +404,13 @@ "metaDescriptionNoVaultShort": "cmux is a free, open source macOS terminal. Pro adds Cloud VMs and a model gateway; Enterprise adds SSO and self-hosting.", "title": "Pricing", "perMonth": "per month", - "perMonthBilledYearly": "per month billed yearly", + "perMonthBilledYearly": "/mo.", "perUserMonth": "per user per month", - "perUserMonthBilledYearly": "per user per month billed yearly", + "perUserMonthBilledYearly": "/user/mo.", "billingPeriod": "Billing period", "monthly": "Monthly", "annual": "Annual", "saveAnnual": "Save {discount}%", - "annualPriceDetail": "Billed ${amount} annually · save {discount}%", "annualComparePrice": "${monthly}/month · ${annual}/year", "teamMonthlyComparePrice": "${monthly}/user/month", "teamAnnualComparePrice": "${monthly}/user/month · ${annual}/user/year", diff --git a/web/messages/ja.json b/web/messages/ja.json index f476a009c56c..49b8b17e84c6 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -404,14 +404,13 @@ "metaDescriptionNoVaultShort": "cmux は無料・オープンソースの macOS ターミナルです。Pro では Cloud VM とモデルゲートウェイを利用でき、Enterprise では SSO とセルフホストに対応します。", "title": "料金", "perMonth": "月額", - "perMonthBilledYearly": "月額(年払い)", + "perMonthBilledYearly": "/月", "perUserMonth": "ユーザーあたり月額", - "perUserMonthBilledYearly": "ユーザーあたり月額(年払い)", + "perUserMonthBilledYearly": "/ユーザー/月", "billingPeriod": "請求期間", "monthly": "月払い", "annual": "年払い", "saveAnnual": "{discount}%割引", - "annualPriceDetail": "年額 ${amount} の一括払い · {discount}%割引", "annualComparePrice": "${monthly}/月 · ${annual}/年", "teamMonthlyComparePrice": "${monthly}/ユーザー/月", "teamAnnualComparePrice": "${monthly}/ユーザー/月 · ${annual}/ユーザー/年", diff --git a/web/tests/app-pricing-page.test.tsx b/web/tests/app-pricing-page.test.tsx index 295c10fc5eb5..e20fc3506de4 100644 --- a/web/tests/app-pricing-page.test.tsx +++ b/web/tests/app-pricing-page.test.tsx @@ -121,10 +121,10 @@ describe("app pricing page", () => { expect(html).toContain("$24"); expect(html).toContain("$28"); - expect(html).toContain("per month billed yearly"); - expect(html).toContain("per user per month billed yearly"); - expect(html).toContain("Billed $288 annually · save 20%"); - expect(html).toContain("Billed $336 annually · save 20%"); + expect(html).toContain("/mo."); + expect(html).toContain("/user/mo."); + expect(html).not.toContain("Billed $288 annually · save 20%"); + expect(html).not.toContain("Billed $336 annually · save 20%"); expect(html).toContain("$28/user/month · $336/user/year"); expect(html).toContain( "http://localhost:9210/api/billing/checkout?plan=pro&cmux_external_browser=1&cmux_scheme=cmux-dev-test&interval=year", diff --git a/web/tests/dashboard-billing-page.test.tsx b/web/tests/dashboard-billing-page.test.tsx index afb6d82d5709..7eae7abf1b1e 100644 --- a/web/tests/dashboard-billing-page.test.tsx +++ b/web/tests/dashboard-billing-page.test.tsx @@ -118,10 +118,10 @@ describe("dashboard billing page", () => { expect(html).toContain("$24"); expect(html).toContain("$28"); - expect(html).toContain("per month billed yearly"); - expect(html).toContain("per user per month billed yearly"); - expect(html).toContain("Billed $288 annually · save 20%"); - expect(html).toContain("Billed $336 annually · save 20%"); + expect(html).toContain("/mo."); + expect(html).toContain("/user/mo."); + expect(html).not.toContain("Billed $288 annually · save 20%"); + expect(html).not.toContain("Billed $336 annually · save 20%"); expect(html).toContain( 'href="/api/billing/checkout?plan=pro&cmux_external_browser=1&interval=year"', ); diff --git a/web/tests/pricing-page.test.tsx b/web/tests/pricing-page.test.tsx index 8590de12d61f..0203c280c22c 100644 --- a/web/tests/pricing-page.test.tsx +++ b/web/tests/pricing-page.test.tsx @@ -118,13 +118,13 @@ describe("localized pricing page", () => { const html = renderToStaticMarkup(element); expect(html).toContain("$24"); - expect(html).toContain("per month billed yearly"); - expect(html).toContain("Billed $288 annually · save 20%"); - expect(html).toContain("Billed $336 annually · save 20%"); + expect(html).toContain("/mo."); expect(html).toContain("$24/month · $288/year"); expect(html).toContain("$28"); - expect(html).toContain("per user per month billed yearly"); + expect(html).toContain("/user/mo."); expect(html).toContain("$28/user/month · $336/user/year"); + expect(html).not.toContain("Billed $288 annually · save 20%"); + expect(html).not.toContain("Billed $336 annually · save 20%"); expect(html).toContain( "/api/billing/checkout?plan=pro&cmux_external_browser=1&interval=year", ); From ff4397ac2350a3e579dc6c5d30ac9909d18cc3dd Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:32:41 -0700 Subject: [PATCH 2/6] Tighten pricing card layout --- web/app/components/pricing-shared.tsx | 7 ++----- web/messages/en.json | 4 ++-- web/messages/ja.json | 4 ++-- web/tests/app-pricing-page.test.tsx | 3 +++ web/tests/dashboard-billing-page.test.tsx | 3 +++ web/tests/pricing-page.test.tsx | 3 +++ 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/web/app/components/pricing-shared.tsx b/web/app/components/pricing-shared.tsx index 4230609ff6bd..ade076fbbda8 100644 --- a/web/app/components/pricing-shared.tsx +++ b/web/app/components/pricing-shared.tsx @@ -66,14 +66,12 @@ export function PlanCard({ name, price, period, - priceDetail, badge, children, }: { name: string; price: ReactNode; period?: ReactNode; - priceDetail?: ReactNode; badge?: ReactNode; children: ReactNode; }) { @@ -81,7 +79,7 @@ export function PlanCard({
{badge ?
{badge}
: null}

{name}

-
+
{price} {period ? ( @@ -90,9 +88,8 @@ export function PlanCard({ ) : null}
- {priceDetail}
-
{children}
+
{children}
); } diff --git a/web/messages/en.json b/web/messages/en.json index e44c9f5e6482..f6635b70c5ec 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -403,9 +403,9 @@ "metaDescriptionNoVault": "cmux is a free, open source macOS terminal for AI coding agents. Upgrade to Pro for Cloud VMs and a model gateway. Enterprise adds SSO and self-hosting.", "metaDescriptionNoVaultShort": "cmux is a free, open source macOS terminal. Pro adds Cloud VMs and a model gateway; Enterprise adds SSO and self-hosting.", "title": "Pricing", - "perMonth": "per month", + "perMonth": "/mo.", "perMonthBilledYearly": "/mo.", - "perUserMonth": "per user per month", + "perUserMonth": "/user/mo.", "perUserMonthBilledYearly": "/user/mo.", "billingPeriod": "Billing period", "monthly": "Monthly", diff --git a/web/messages/ja.json b/web/messages/ja.json index 49b8b17e84c6..cdd68262dafd 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -403,9 +403,9 @@ "metaDescriptionNoVault": "cmux はAIコーディングエージェント向けの無料・オープンソースの macOS ターミナルです。Pro では Cloud VM とモデルゲートウェイが使えます。Enterprise は SSO とセルフホストに対応します。", "metaDescriptionNoVaultShort": "cmux は無料・オープンソースの macOS ターミナルです。Pro では Cloud VM とモデルゲートウェイを利用でき、Enterprise では SSO とセルフホストに対応します。", "title": "料金", - "perMonth": "月額", + "perMonth": "/月", "perMonthBilledYearly": "/月", - "perUserMonth": "ユーザーあたり月額", + "perUserMonth": "/ユーザー/月", "perUserMonthBilledYearly": "/ユーザー/月", "billingPeriod": "請求期間", "monthly": "月払い", diff --git a/web/tests/app-pricing-page.test.tsx b/web/tests/app-pricing-page.test.tsx index e20fc3506de4..848b5f5cd647 100644 --- a/web/tests/app-pricing-page.test.tsx +++ b/web/tests/app-pricing-page.test.tsx @@ -96,8 +96,11 @@ describe("app pricing page", () => { expect(html).toContain( "http://localhost:9210/api/billing/checkout?plan=team&cmux_external_browser=1&cmux_scheme=cmux-dev-test", ); + expect(html).toContain("/mo."); + expect(html).toContain("/user/mo."); expect(html).toContain("$35/user/month"); expect(html).toContain('

Includes:

'); + expect(html).not.toContain('style="min-height:4rem"'); expect(html).toContain("sm:grid-cols-2 min-[1800px]:grid-cols-4"); expect(html.split("api/billing/checkout?plan=pro")).toHaveLength(2); expect(html.split("api/billing/checkout?plan=team")).toHaveLength(2); diff --git a/web/tests/dashboard-billing-page.test.tsx b/web/tests/dashboard-billing-page.test.tsx index 7eae7abf1b1e..da02262e3d4f 100644 --- a/web/tests/dashboard-billing-page.test.tsx +++ b/web/tests/dashboard-billing-page.test.tsx @@ -107,6 +107,9 @@ describe("dashboard billing page", () => { ); expect(html).toContain("Get Pro"); expect(html).toContain("Get Teams"); + expect(html).toContain("/mo."); + expect(html).toContain("/user/mo."); + expect(html).not.toContain('style="min-height:4rem"'); expect(html).toContain('href="/dashboard/testflight"'); expect(html).toContain("Join the iOS beta"); expect(html).toContain("active personal Pro subscribers"); diff --git a/web/tests/pricing-page.test.tsx b/web/tests/pricing-page.test.tsx index 0203c280c22c..4729d2213a7d 100644 --- a/web/tests/pricing-page.test.tsx +++ b/web/tests/pricing-page.test.tsx @@ -79,11 +79,14 @@ describe("localized pricing page", () => { expect(html).not.toContain("/api/billing/portal"); expect(html).not.toContain("Manage billing"); + expect(html).toContain("/mo."); + expect(html).toContain("/user/mo."); expect(html).toContain("$35/user/month"); expect(html).toContain( "/api/billing/checkout?plan=team&cmux_external_browser=1&interval=month", ); expect(html).toContain('

Includes:

'); + expect(html).not.toContain('style="min-height:4rem"'); }); test("renders Stack metadata-only Pro snapshots as Free", async () => { From 6502a49a3b8b1ef7135a878277d07621fb17f685 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:43:16 -0700 Subject: [PATCH 3/6] Align pricing numerals and app grid --- web/app/app-pricing/page.tsx | 2 +- web/app/components/pricing-shared.tsx | 10 +++++++--- web/tests/app-pricing-page.test.tsx | 3 ++- web/tests/dashboard-billing-page.test.tsx | 1 + web/tests/pricing-page.test.tsx | 1 + 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/web/app/app-pricing/page.tsx b/web/app/app-pricing/page.tsx index d10beb657de8..7cb57cd45529 100644 --- a/web/app/app-pricing/page.tsx +++ b/web/app/app-pricing/page.tsx @@ -129,7 +129,7 @@ export default async function AppPricingPage({ surface="app_pricing" /> -
+
{name}
- {price} + + {price} + {period ? ( {period} @@ -284,7 +286,7 @@ export function PricingSizeTable({ {row.use} - + {row.rate} @@ -308,7 +310,9 @@ function ColumnHead({ return (
{name} - {price} + + {price} + {action ?
{action}
: null}
); diff --git a/web/tests/app-pricing-page.test.tsx b/web/tests/app-pricing-page.test.tsx index 848b5f5cd647..78b3d58e2334 100644 --- a/web/tests/app-pricing-page.test.tsx +++ b/web/tests/app-pricing-page.test.tsx @@ -101,7 +101,8 @@ describe("app pricing page", () => { expect(html).toContain("$35/user/month"); expect(html).toContain('

Includes:

'); expect(html).not.toContain('style="min-height:4rem"'); - expect(html).toContain("sm:grid-cols-2 min-[1800px]:grid-cols-4"); + expect(html).toContain("text-3xl font-medium tabular-nums tracking-tight"); + expect(html).toContain("sm:grid-cols-2 lg:grid-cols-4"); expect(html.split("api/billing/checkout?plan=pro")).toHaveLength(2); expect(html.split("api/billing/checkout?plan=team")).toHaveLength(2); expect(html).toContain("Compare plans"); diff --git a/web/tests/dashboard-billing-page.test.tsx b/web/tests/dashboard-billing-page.test.tsx index da02262e3d4f..bad3cea0038b 100644 --- a/web/tests/dashboard-billing-page.test.tsx +++ b/web/tests/dashboard-billing-page.test.tsx @@ -110,6 +110,7 @@ describe("dashboard billing page", () => { expect(html).toContain("/mo."); expect(html).toContain("/user/mo."); expect(html).not.toContain('style="min-height:4rem"'); + expect(html).toContain("text-3xl font-medium tabular-nums tracking-tight"); expect(html).toContain('href="/dashboard/testflight"'); expect(html).toContain("Join the iOS beta"); expect(html).toContain("active personal Pro subscribers"); diff --git a/web/tests/pricing-page.test.tsx b/web/tests/pricing-page.test.tsx index 4729d2213a7d..0c92de2cfb21 100644 --- a/web/tests/pricing-page.test.tsx +++ b/web/tests/pricing-page.test.tsx @@ -87,6 +87,7 @@ describe("localized pricing page", () => { ); expect(html).toContain('

Includes:

'); expect(html).not.toContain('style="min-height:4rem"'); + expect(html).toContain("text-3xl font-medium tabular-nums tracking-tight"); }); test("renders Stack metadata-only Pro snapshots as Free", async () => { From 61bfa204137da0e576b053ff89463552bf6a4c9c Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:53:34 -0700 Subject: [PATCH 4/6] Remove annual pricing totals --- web/app/[locale]/pricing/page.tsx | 2 -- web/app/app-pricing/page.tsx | 2 -- web/messages/en.json | 28 ++++++++++----------- web/messages/ja.json | 14 +++++------ web/tests/app-pricing-page.test.tsx | 16 +++++++----- web/tests/dashboard-billing-page.test.tsx | 30 +++++++++++++---------- web/tests/pricing-page.test.tsx | 17 +++++++------ 7 files changed, 58 insertions(+), 51 deletions(-) diff --git a/web/app/[locale]/pricing/page.tsx b/web/app/[locale]/pricing/page.tsx index 8335fa518cbc..ade4402c5812 100644 --- a/web/app/[locale]/pricing/page.tsx +++ b/web/app/[locale]/pricing/page.tsx @@ -111,14 +111,12 @@ export default async function PricingPage({ }; const annualComparePrice = t("annualComparePrice", { monthly: PRO_PRICING_USD.year.monthlyEquivalent, - annual: PRO_PRICING_USD.year.billedAmount, }); const teamMonthlyComparePrice = t("teamMonthlyComparePrice", { monthly: TEAM_PRICING_USD.month.monthlyEquivalent, }); const teamAnnualComparePrice = t("teamAnnualComparePrice", { monthly: TEAM_PRICING_USD.year.monthlyEquivalent, - annual: TEAM_PRICING_USD.year.billedAmount, }); const freeFeatures = t.raw("free.features") as string[]; diff --git a/web/app/app-pricing/page.tsx b/web/app/app-pricing/page.tsx index 7cb57cd45529..5c5c25664a26 100644 --- a/web/app/app-pricing/page.tsx +++ b/web/app/app-pricing/page.tsx @@ -86,7 +86,6 @@ export default async function AppPricingPage({ const faqItems = visibleFaqItems(pricing.faq.items as FaqItem[]); const annualComparePrice = pricingMessage(pricing.annualComparePrice, { monthly: PRO_PRICING_USD.year.monthlyEquivalent, - annual: PRO_PRICING_USD.year.billedAmount, }); const teamMonthlyComparePrice = pricingMessage( pricing.teamMonthlyComparePrice, @@ -96,7 +95,6 @@ export default async function AppPricingPage({ pricing.teamAnnualComparePrice, { monthly: TEAM_PRICING_USD.year.monthlyEquivalent, - annual: TEAM_PRICING_USD.year.billedAmount, }, ); diff --git a/web/messages/en.json b/web/messages/en.json index f6635b70c5ec..b960fd7a2ba5 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -143,17 +143,17 @@ "name": "cmux Pro", "activeBody": "Your plan renews on {date}.", "pendingBody": "Your plan is scheduled to end on {date}.", - "monthlyPrice": "$30/month", - "annualPrice": "$288/year", - "legacyAnnualPrice": "$240/year" + "monthlyPrice": "$30/mo", + "annualPrice": "$24/mo, billed annually", + "legacyAnnualPrice": "$20/mo, billed annually" }, "team": { "name": "cmux Team", "fallbackName": "your team", "activeBody": "{team} renews on {date}.", "pendingBody": "{team} is scheduled to end on {date}.", - "price": "$35/seat/month", - "annualPrice": "$336/seat/year" + "price": "$35/seat/mo", + "annualPrice": "$28/seat/mo, billed annually" }, "details": { "renewsOn": "Renews on", @@ -403,17 +403,17 @@ "metaDescriptionNoVault": "cmux is a free, open source macOS terminal for AI coding agents. Upgrade to Pro for Cloud VMs and a model gateway. Enterprise adds SSO and self-hosting.", "metaDescriptionNoVaultShort": "cmux is a free, open source macOS terminal. Pro adds Cloud VMs and a model gateway; Enterprise adds SSO and self-hosting.", "title": "Pricing", - "perMonth": "/mo.", - "perMonthBilledYearly": "/mo.", - "perUserMonth": "/user/mo.", - "perUserMonthBilledYearly": "/user/mo.", + "perMonth": "/mo", + "perMonthBilledYearly": "/mo", + "perUserMonth": "/user/mo", + "perUserMonthBilledYearly": "/user/mo", "billingPeriod": "Billing period", "monthly": "Monthly", "annual": "Annual", "saveAnnual": "Save {discount}%", - "annualComparePrice": "${monthly}/month · ${annual}/year", - "teamMonthlyComparePrice": "${monthly}/user/month", - "teamAnnualComparePrice": "${monthly}/user/month · ${annual}/user/year", + "annualComparePrice": "${monthly}/mo", + "teamMonthlyComparePrice": "${monthly}/user/mo", + "teamAnnualComparePrice": "${monthly}/user/mo", "currentPlan": "Current plan", "manageBilling": "Manage billing", "free": { @@ -671,7 +671,7 @@ }, { "q": "How does billing work?", - "a": "Pro is $30 per month, or $288 per year ($24 per month billed annually, a 20% savings), and includes 20 active compute-hours per month on the 4 vCPU / 16 GB Cloud VM. Beyond that, cloud usage is billed per active compute-hour by VM size (see Cloud VM sizes above). Idle VMs suspend automatically, and running agents in parallel just uses more hours. The terminal and local agents stay free." + "a": "Pro is $30/mo, or $24/mo when billed annually (a 20% savings), and includes 20 active compute-hours per month on the 4 vCPU / 16 GB Cloud VM. Beyond that, cloud usage is billed per active compute-hour by VM size (see Cloud VM sizes above). Idle VMs suspend automatically, and running agents in parallel just uses more hours. The terminal and local agents stay free." }, { "q": "Can I self-host the cloud?", @@ -679,7 +679,7 @@ }, { "q": "Do you have a team plan?", - "a": "Yes. Team is $35 per user per month, or $336 per user per year ($28 per user per month billed annually): unified billing for the whole team, centralized seat management, pooled Cloud VM hours, and shared team rules. Enterprise adds SSO, self-hosting, and audit logs." + "a": "Yes. Team is $35/user/mo, or $28/user/mo when billed annually: unified billing for the whole team, centralized seat management, pooled Cloud VM hours, and shared team rules. Enterprise adds SSO, self-hosting, and audit logs." } ] }, diff --git a/web/messages/ja.json b/web/messages/ja.json index cdd68262dafd..905396b9a84b 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -144,8 +144,8 @@ "activeBody": "プランは{date}に更新されます。", "pendingBody": "プランは{date}に終了する予定です。", "monthlyPrice": "$30/月", - "annualPrice": "$288/年", - "legacyAnnualPrice": "$240/年" + "annualPrice": "$24/月(年払い)", + "legacyAnnualPrice": "$20/月(年払い)" }, "team": { "name": "cmux Team", @@ -153,7 +153,7 @@ "activeBody": "{team}は{date}に更新されます。", "pendingBody": "{team}は{date}に終了する予定です。", "price": "$35/シート/月", - "annualPrice": "$336/シート/年" + "annualPrice": "$28/シート/月(年払い)" }, "details": { "renewsOn": "更新日", @@ -411,9 +411,9 @@ "monthly": "月払い", "annual": "年払い", "saveAnnual": "{discount}%割引", - "annualComparePrice": "${monthly}/月 · ${annual}/年", + "annualComparePrice": "${monthly}/月", "teamMonthlyComparePrice": "${monthly}/ユーザー/月", - "teamAnnualComparePrice": "${monthly}/ユーザー/月 · ${annual}/ユーザー/年", + "teamAnnualComparePrice": "${monthly}/ユーザー/月", "currentPlan": "現在のプラン", "manageBilling": "請求を管理", "free": { @@ -671,7 +671,7 @@ }, { "q": "課金はどのように行われますか?", - "a": "Pro は月額 $30、または年額 $288(年払いでは月あたり $24、20%割引)で、4 vCPU / 16 GB の Cloud VM で毎月20時間のアクティブ計算時間が含まれます。それを超えると、VM サイズに応じてアクティブな計算時間あたりで課金されます(上記の Cloud VM サイズを参照)。アイドルの VM は自動的に一時停止し、エージェントを並列実行するとより多くの時間を使うだけです。ターミナルとローカルエージェントは無料のままです。" + "a": "Pro は月払いで $30/月、年払いでは $24/月(20%割引)で、4 vCPU / 16 GB の Cloud VM で毎月20時間のアクティブ計算時間が含まれます。それを超えると、VM サイズに応じてアクティブな計算時間あたりで課金されます(上記の Cloud VM サイズを参照)。アイドルの VM は自動的に一時停止し、エージェントを並列実行するとより多くの時間を使うだけです。ターミナルとローカルエージェントは無料のままです。" }, { "q": "クラウドをセルフホストできますか?", @@ -679,7 +679,7 @@ }, { "q": "チームプランはありますか?", - "a": "はい。Team は1ユーザーあたり月額 $35、または年額 $336(月額換算 $28、20%割引)です。チーム全体の一元請求、シート管理、Cloud VM 時間のプール、共有チームルールが含まれます。Enterprise では SSO、セルフホスト、監査ログが追加されます。" + "a": "はい。Team は月払いで $35/ユーザー/月、年払いでは $28/ユーザー/月です。チーム全体の一元請求、シート管理、Cloud VM 時間のプール、共有チームルールが含まれます。Enterprise では SSO、セルフホスト、監査ログが追加されます。" } ] }, diff --git a/web/tests/app-pricing-page.test.tsx b/web/tests/app-pricing-page.test.tsx index 78b3d58e2334..fee88e3c47d6 100644 --- a/web/tests/app-pricing-page.test.tsx +++ b/web/tests/app-pricing-page.test.tsx @@ -96,9 +96,10 @@ describe("app pricing page", () => { expect(html).toContain( "http://localhost:9210/api/billing/checkout?plan=team&cmux_external_browser=1&cmux_scheme=cmux-dev-test", ); - expect(html).toContain("/mo."); - expect(html).toContain("/user/mo."); - expect(html).toContain("$35/user/month"); + expect(html).toContain("/mo"); + expect(html).toContain("/user/mo"); + expect(html).not.toContain("/mo."); + expect(html).toContain("$35/user/mo"); expect(html).toContain('

Includes:

'); expect(html).not.toContain('style="min-height:4rem"'); expect(html).toContain("text-3xl font-medium tabular-nums tracking-tight"); @@ -125,11 +126,14 @@ describe("app pricing page", () => { expect(html).toContain("$24"); expect(html).toContain("$28"); - expect(html).toContain("/mo."); - expect(html).toContain("/user/mo."); + expect(html).toContain("/mo"); + expect(html).toContain("/user/mo"); + expect(html).not.toContain("/mo."); expect(html).not.toContain("Billed $288 annually · save 20%"); expect(html).not.toContain("Billed $336 annually · save 20%"); - expect(html).toContain("$28/user/month · $336/user/year"); + expect(html).toContain("$28/user/mo"); + expect(html).not.toContain("$288/year"); + expect(html).not.toContain("$336/user/year"); expect(html).toContain( "http://localhost:9210/api/billing/checkout?plan=pro&cmux_external_browser=1&cmux_scheme=cmux-dev-test&interval=year", ); diff --git a/web/tests/dashboard-billing-page.test.tsx b/web/tests/dashboard-billing-page.test.tsx index bad3cea0038b..86245b4c2e8d 100644 --- a/web/tests/dashboard-billing-page.test.tsx +++ b/web/tests/dashboard-billing-page.test.tsx @@ -107,8 +107,9 @@ describe("dashboard billing page", () => { ); expect(html).toContain("Get Pro"); expect(html).toContain("Get Teams"); - expect(html).toContain("/mo."); - expect(html).toContain("/user/mo."); + expect(html).toContain("/mo"); + expect(html).toContain("/user/mo"); + expect(html).not.toContain("/mo."); expect(html).not.toContain('style="min-height:4rem"'); expect(html).toContain("text-3xl font-medium tabular-nums tracking-tight"); expect(html).toContain('href="/dashboard/testflight"'); @@ -122,8 +123,9 @@ describe("dashboard billing page", () => { expect(html).toContain("$24"); expect(html).toContain("$28"); - expect(html).toContain("/mo."); - expect(html).toContain("/user/mo."); + expect(html).toContain("/mo"); + expect(html).toContain("/user/mo"); + expect(html).not.toContain("/mo."); expect(html).not.toContain("Billed $288 annually · save 20%"); expect(html).not.toContain("Billed $336 annually · save 20%"); expect(html).toContain( @@ -142,7 +144,7 @@ describe("dashboard billing page", () => { expect(html).toContain("cmux Pro"); expect(html).toContain("Your plan renews on"); - expect(html).toContain("$30/month"); + expect(html).toContain("$30/mo"); expect(html).toContain("Cancel plan"); expect(html).toContain('action="/api/billing/subscription"'); expect(html).toContain('href="/api/billing/portal"'); @@ -156,7 +158,7 @@ describe("dashboard billing page", () => { }), ]; customerRows = [{ id: "cus_123" }]; - expect(await renderBillingPage()).toContain("$288/year"); + expect(await renderBillingPage()).toContain("$24/mo, billed annually"); subscriptionRows = [ stripeSubscriptionRow({ @@ -164,7 +166,7 @@ describe("dashboard billing page", () => { lookupKey: "cmux-pro-yearly", }), ]; - expect(await renderBillingPage()).toContain("$240/year"); + expect(await renderBillingPage()).toContain("$20/mo, billed annually"); }); test("renders pending cancellation with resume and end-date copy", async () => { @@ -201,7 +203,7 @@ describe("dashboard billing page", () => { expect(html).toContain("Team Pro renews on"); expect(html).toContain("Seats"); expect(html).toContain(">4<"); - expect(html).toContain("$35/seat/month"); + expect(html).toContain("$35/seat/mo"); expect(html).toContain('name="scope" value="team"'); expect(html).toContain('href="/api/billing/portal?scope=team"'); }); @@ -223,7 +225,7 @@ describe("dashboard billing page", () => { ]; customerRows = [{ id: "cus_team" }]; - expect(await renderBillingPage()).toContain("$336/seat/year"); + expect(await renderBillingPage()).toContain("$28/seat/mo, billed annually"); }); test("uses the current Stripe price interval over stale checkout metadata", async () => { @@ -244,7 +246,7 @@ describe("dashboard billing page", () => { ]; customerRows = [{ id: "cus_team" }]; - expect(await renderBillingPage()).toContain("$35/seat/month"); + expect(await renderBillingPage()).toContain("$35/seat/mo"); subscriptionResults = [ [], @@ -260,12 +262,14 @@ describe("dashboard billing page", () => { }), ], ]; - expect(await renderBillingPage()).toContain("$336/seat/year"); + expect(await renderBillingPage()).toContain("$28/seat/mo, billed annually"); }); test("localizes active annual Pro prices", () => { - expect(enMessages.dashboard.billing.pro.annualPrice).toBe("$288/year"); - expect(jaMessages.dashboard.billing.pro.annualPrice).toBe("$288/年"); + expect(enMessages.dashboard.billing.pro.annualPrice).toBe( + "$24/mo, billed annually", + ); + expect(jaMessages.dashboard.billing.pro.annualPrice).toBe("$24/月(年払い)"); }); test("renders active Stripe Team for a paid team when no team is selected", async () => { diff --git a/web/tests/pricing-page.test.tsx b/web/tests/pricing-page.test.tsx index 0c92de2cfb21..3a067fce9544 100644 --- a/web/tests/pricing-page.test.tsx +++ b/web/tests/pricing-page.test.tsx @@ -79,9 +79,10 @@ describe("localized pricing page", () => { expect(html).not.toContain("/api/billing/portal"); expect(html).not.toContain("Manage billing"); - expect(html).toContain("/mo."); - expect(html).toContain("/user/mo."); - expect(html).toContain("$35/user/month"); + expect(html).toContain("/mo"); + expect(html).toContain("/user/mo"); + expect(html).not.toContain("/mo."); + expect(html).toContain("$35/user/mo"); expect(html).toContain( "/api/billing/checkout?plan=team&cmux_external_browser=1&interval=month", ); @@ -122,11 +123,13 @@ describe("localized pricing page", () => { const html = renderToStaticMarkup(element); expect(html).toContain("$24"); - expect(html).toContain("/mo."); - expect(html).toContain("$24/month · $288/year"); + expect(html).toContain("/mo"); + expect(html).toContain("$24/mo"); expect(html).toContain("$28"); - expect(html).toContain("/user/mo."); - expect(html).toContain("$28/user/month · $336/user/year"); + expect(html).toContain("/user/mo"); + expect(html).toContain("$28/user/mo"); + expect(html).not.toContain("$288/year"); + expect(html).not.toContain("$336/user/year"); expect(html).not.toContain("Billed $288 annually · save 20%"); expect(html).not.toContain("Billed $336 annually · save 20%"); expect(html).toContain( From 5a4b88646ac034fdf52dfbf257e0f915e569a2a3 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 1 Aug 2026 03:02:01 -0700 Subject: [PATCH 5/6] Add annual billing cadence regression coverage --- web/tests/app-pricing-page.test.tsx | 2 ++ web/tests/dashboard-billing-page.test.tsx | 2 ++ web/tests/pricing-page.test.tsx | 2 ++ 3 files changed, 6 insertions(+) diff --git a/web/tests/app-pricing-page.test.tsx b/web/tests/app-pricing-page.test.tsx index fee88e3c47d6..ddcd5a518a6b 100644 --- a/web/tests/app-pricing-page.test.tsx +++ b/web/tests/app-pricing-page.test.tsx @@ -128,6 +128,8 @@ describe("app pricing page", () => { expect(html).toContain("$28"); expect(html).toContain("/mo"); expect(html).toContain("/user/mo"); + expect(html).toContain("/mo, billed yearly"); + expect(html).toContain("/user/mo, billed yearly"); expect(html).not.toContain("/mo."); expect(html).not.toContain("Billed $288 annually · save 20%"); expect(html).not.toContain("Billed $336 annually · save 20%"); diff --git a/web/tests/dashboard-billing-page.test.tsx b/web/tests/dashboard-billing-page.test.tsx index 86245b4c2e8d..1236b090977f 100644 --- a/web/tests/dashboard-billing-page.test.tsx +++ b/web/tests/dashboard-billing-page.test.tsx @@ -125,6 +125,8 @@ describe("dashboard billing page", () => { expect(html).toContain("$28"); expect(html).toContain("/mo"); expect(html).toContain("/user/mo"); + expect(html).toContain("/mo, billed yearly"); + expect(html).toContain("/user/mo, billed yearly"); expect(html).not.toContain("/mo."); expect(html).not.toContain("Billed $288 annually · save 20%"); expect(html).not.toContain("Billed $336 annually · save 20%"); diff --git a/web/tests/pricing-page.test.tsx b/web/tests/pricing-page.test.tsx index 3a067fce9544..f53763dca17d 100644 --- a/web/tests/pricing-page.test.tsx +++ b/web/tests/pricing-page.test.tsx @@ -127,6 +127,8 @@ describe("localized pricing page", () => { expect(html).toContain("$24/mo"); expect(html).toContain("$28"); expect(html).toContain("/user/mo"); + expect(html).toContain("/mo, billed yearly"); + expect(html).toContain("/user/mo, billed yearly"); expect(html).toContain("$28/user/mo"); expect(html).not.toContain("$288/year"); expect(html).not.toContain("$336/user/year"); From bb8d4b3b15253b53e9a33ead6d94914ff0b71c31 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 1 Aug 2026 03:04:22 -0700 Subject: [PATCH 6/6] Disclose yearly billing in compact labels --- web/messages/en.json | 4 ++-- web/messages/ja.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/messages/en.json b/web/messages/en.json index b960fd7a2ba5..9dd69e2f9427 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -404,9 +404,9 @@ "metaDescriptionNoVaultShort": "cmux is a free, open source macOS terminal. Pro adds Cloud VMs and a model gateway; Enterprise adds SSO and self-hosting.", "title": "Pricing", "perMonth": "/mo", - "perMonthBilledYearly": "/mo", + "perMonthBilledYearly": "/mo, billed yearly", "perUserMonth": "/user/mo", - "perUserMonthBilledYearly": "/user/mo", + "perUserMonthBilledYearly": "/user/mo, billed yearly", "billingPeriod": "Billing period", "monthly": "Monthly", "annual": "Annual", diff --git a/web/messages/ja.json b/web/messages/ja.json index 905396b9a84b..a8574dda7212 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -404,9 +404,9 @@ "metaDescriptionNoVaultShort": "cmux は無料・オープンソースの macOS ターミナルです。Pro では Cloud VM とモデルゲートウェイを利用でき、Enterprise では SSO とセルフホストに対応します。", "title": "料金", "perMonth": "/月", - "perMonthBilledYearly": "/月", + "perMonthBilledYearly": "/月(年払い)", "perUserMonth": "/ユーザー/月", - "perUserMonthBilledYearly": "/ユーザー/月", + "perUserMonthBilledYearly": "/ユーザー/月(年払い)", "billingPeriod": "請求期間", "monthly": "月払い", "annual": "年払い",