Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const FEATURES: Feature[] = [
tag: "Remote Workspaces",
title: "Run workspaces anywhere",
description:
"Add any machine as a host, on every plan. Workspaces keep running when your laptop sleeps, and you can check in from wherever you are.",
"Add any machine as a host. Workspaces keep running when your laptop sleeps, and you can check in from wherever you are.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the homepage claim that Remote Workspaces are available on every plan.

Line 42 removes on every plan. FeaturesSection.tsx renders this description directly, so the homepage no longer contains the claim required by the PR objective. Restore the phrase, or update the objective if the product requirement changed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/marketing/src/app/components/FeaturesSection/constants.ts` at line 42,
Restore the “on every plan” phrase in the Remote Workspaces description constant
used by FeaturesSection.tsx, preserving the rest of the existing copy and
homepage claim.

},
{
tag: "CLI & SDK",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,21 @@ export function ComparisonTable() {
function DesktopTable() {
return (
<div className="hidden md:block">
<table className="w-full table-fixed border-collapse">
<table className="w-full table-fixed border-separate border-spacing-0">
<thead>
<tr className="border-b border-border">
<th className="w-2/5 py-4 pr-4 text-left text-sm font-medium text-muted-foreground">
<tr>
<th className="sticky top-16 z-10 w-2/5 border-b border-border bg-background py-4 pr-4 text-left text-sm font-medium text-muted-foreground">
Features
</th>
{PRICING_TIERS.map((tier) => (
<th
key={tier.id}
className="w-1/5 py-4 px-4 text-left text-sm font-medium text-foreground"
className="sticky top-16 z-10 w-1/5 border-b border-border bg-background py-4 px-4 text-left text-sm font-medium text-foreground"
>
{tier.name}
<span className="ml-2 font-normal text-xs text-muted-foreground">
{tierPriceLabel(tier)}
</span>
</th>
))}
</tr>
Expand All @@ -60,6 +63,16 @@ function DesktopTable() {
);
}

function tierPriceLabel(tier: (typeof PRICING_TIERS)[number]): string {
if (tier.price.kind === "fixed") {
return tier.price.display;
}
if (tier.price.kind === "variable") {
return `from ${tier.price.yearly.display}/user/mo`;
}
return "Custom";
}
Comment on lines +66 to +74

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include the yearly billing cadence in the comparison header.

tierPriceLabel always selects tier.price.yearly.display but appends /user/mo. For Pro, Line 65 of apps/marketing/src/app/pricing/constants.ts defines the price as "$180 per user, billed yearly". The sticky header can therefore be read as a $15 monthly charge.

If the table is annual-only, include the yearly billing qualifier or render the annual total. If it must follow the billing selection, pass that selection instead of hardcoding yearly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/marketing/src/app/pricing/components/ComparisonTable/ComparisonTable.tsx`
around lines 66 - 74, Update tierPriceLabel to accurately communicate the annual
billing cadence: either append a yearly qualifier when using
tier.price.yearly.display or render the annual total instead of implying a
monthly charge. If billing selection is supported elsewhere, pass that selection
into tierPriceLabel and derive the displayed price from it rather than
hardcoding yearly.


function DesktopSectionGroup({
title,
children,
Expand All @@ -84,8 +97,8 @@ function DesktopSectionGroup({

function DesktopRow({ row }: { row: ComparisonRow }) {
return (
<tr className="border-b border-border/60">
<td className="py-4 pr-4 text-sm text-foreground">
<tr>
<td className="border-b border-border/60 py-4 pr-4 text-sm text-foreground">
<div className="flex items-center gap-2">
<span>{row.label}</span>
{row.badge && <RowBadge badge={row.badge} />}
Expand All @@ -94,7 +107,7 @@ function DesktopRow({ row }: { row: ComparisonRow }) {
{row.values.map((value, index) => (
<td
key={`${row.label}-${index}`}
className="px-4 py-4 text-sm text-foreground"
className="border-b border-border/60 px-4 py-4 text-sm text-foreground"
>
<Cell value={value} />
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@ export function PricingCard({ tier, isYearly }: PricingCardProps) {
{cadence && <p className="text-xs text-muted-foreground">{cadence}</p>}
</div>

<Button asChild variant={tier.cta.variant} size="lg" className="w-full">
{tier.cta.external ? (
<a href={tier.cta.href} target="_blank" rel="noopener noreferrer">
{tier.cta.label}
</a>
) : (
<Link href={tier.cta.href}>{tier.cta.label}</Link>
)}
</Button>
<div className="flex flex-col gap-2">
<Button asChild variant={tier.cta.variant} size="lg" className="w-full">
{tier.cta.external ? (
<a href={tier.cta.href} target="_blank" rel="noopener noreferrer">
{tier.cta.label}
</a>
) : (
<Link href={tier.cta.href}>{tier.cta.label}</Link>
)}
</Button>
<CtaNote note={tier.ctaNote} />
</div>

<ul className="flex flex-col gap-3 border-t border-border pt-6">
{tier.features.map((feature) => (
Expand All @@ -80,6 +83,27 @@ export function PricingCard({ tier, isYearly }: PricingCardProps) {
);
}

function CtaNote({ note }: { note: PricingTier["ctaNote"] }) {
if (!note) {
return <p className="h-4" aria-hidden="true" />;
}
if (note.href) {
return (
<a
href={note.href}
target="_blank"
rel="noopener noreferrer"
className="text-center text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground"
>
{note.label}
</a>
);
}
return (
<p className="text-center text-xs text-muted-foreground">{note.label}</p>
);
}
Comment on lines +86 to +105

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move CtaNote into its own component file.

CtaNote is a React component in the same file as PricingCard. Move it to CtaNote/CtaNote.tsx and add a CtaNote/index.ts barrel export. Import it into PricingCard.tsx.

As per coding guidelines, use one component per file and organize components as ComponentName/ComponentName.tsx with an index.ts barrel export.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx`
around lines 86 - 105, Move the CtaNote component from PricingCard.tsx into a
new CtaNote/CtaNote.tsx file, preserving its existing props, rendering, and
styling. Add a CtaNote/index.ts barrel export, then import CtaNote into
PricingCard.tsx and remove the local definition.

Source: Coding guidelines


function resolvePrice(tier: PricingTier, isYearly: boolean) {
if (tier.price.kind === "fixed") {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Image from "next/image";

const STRIP_LOGOS = [
{
name: "microsoft",
label: "Microsoft",
logo: "/logos/microsoft-wordmark.svg",
height: 18,
},
{
name: "openai",
label: "OpenAI",
logo: "/logos/openai-wordmark.svg",
height: 18,
},
{ name: "google", label: "Google", logo: "/logos/google.svg", height: 22 },
{
name: "vercel",
label: "Vercel",
logo: "/logos/vercel-wordmark.svg",
height: 34,
},
{
name: "datadog",
label: "Datadog",
logo: "/logos/datadog-wordmark.svg",
height: 38,
},
{ name: "amazon", label: "Amazon", logo: "/logos/amazon.png", height: 20 },
];

export function TrustStrip() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: New TrustStrip largely duplicates the existing TrustedBySection logo-wall component and its logo config, only with a subset of the same logos and heading. Reuse TrustedBySection (or extract shared logo data) so the logo list lives in one place and stays consistent as logos change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/marketing/src/app/pricing/components/TrustStrip/TrustStrip.tsx, line 32:

<comment>New TrustStrip largely duplicates the existing TrustedBySection logo-wall component and its logo config, only with a subset of the same logos and heading. Reuse TrustedBySection (or extract shared logo data) so the logo list lives in one place and stays consistent as logos change.</comment>

<file context>
@@ -0,0 +1,52 @@
+	{ name: "amazon", label: "Amazon", logo: "/logos/amazon.png", height: 20 },
+];
+
+export function TrustStrip() {
+	return (
+		<div className="flex flex-col items-center gap-6">
</file context>

return (
<div className="flex flex-col items-center gap-6">
<p className="text-sm text-muted-foreground">Trusted by builders from</p>
<div className="flex flex-wrap items-center justify-center gap-x-10 gap-y-4 opacity-70">
{STRIP_LOGOS.map((client) => (
<Image
key={client.name}
src={client.logo}
alt={client.label}
width={160}
height={client.height}
className="object-contain grayscale brightness-0 invert"
style={{ height: client.height, width: "auto" }}
unoptimized
/>
))}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TrustStrip } from "./TrustStrip";
27 changes: 21 additions & 6 deletions apps/marketing/src/app/pricing/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { COMPANY } from "@superset/shared/constants";
import type { FAQItem } from "@/app/components/FAQSection";

export type TierId = "free" | "pro" | "enterprise";
Expand All @@ -22,6 +23,7 @@ export interface PricingTier {
variant: "default" | "outline" | "secondary";
external?: boolean;
};
ctaNote?: { label: string; href?: string };
highlight?: boolean;
}

Expand All @@ -36,13 +38,14 @@ export const PRICING_TIERS: PricingTier[] = [
"Local workspaces",
"Desktop app",
"GitHub integration",
"CLI (coming soon)",
"CLI",
],
cta: {
label: "Download app",
href: "/download",
variant: "outline",
},
ctaNote: { label: "No credit card required." },
},
{
id: "pro",
Expand All @@ -58,14 +61,15 @@ export const PRICING_TIERS: PricingTier[] = [
yearly: {
display: "$15",
note: "per user/month",
cadence: "Billed yearly",
cadence: "$180 per user, billed yearly",
},
},
features: [
"Everything in Free",
"Unlimited users",
"Remote workspaces",
"Linear integration",
"Slack integration",
"Mobile (coming soon)",
],
cta: {
Expand All @@ -82,20 +86,21 @@ export const PRICING_TIERS: PricingTier[] = [
price: {
kind: "custom",
display: "Custom pricing",
note: "Billed yearly",
note: "Annual billing only",
},
features: [
"Everything in Pro",
"SSO & advanced security",
"SAML SSO & SCIM provisioning",
"Audit logs",
"SLA & dedicated support",
"Uptime SLA & dedicated support",
"Custom integrations",
],
cta: {
label: "Contact sales",
href: "/enterprise",
variant: "outline",
},
ctaNote: { label: "Review our security", href: COMPANY.TRUST_URL },
},
];

Expand Down Expand Up @@ -171,7 +176,7 @@ export const PRICING_FAQ_ITEMS: FAQItem[] = [
{
question: "Is there a free plan?",
answer:
"Yes. Free covers individuals with 1 user, local workspaces, the desktop app, and GitHub integration. No credit card required.",
"Yes. Free covers individuals with 1 user, local workspaces, the desktop app, the CLI, and GitHub integration. No credit card required.",
},
{
question: "How does Pro pricing work?",
Expand All @@ -188,4 +193,14 @@ export const PRICING_FAQ_ITEMS: FAQItem[] = [
answer:
"Everything in Pro plus SSO & SAML, SCIM provisioning, IP restrictions, audit logs, a custom SLA, dedicated support, and custom contracts. Pricing is tailored to your organization. Get in touch and we'll scope something that fits.",
},
{
question: "Where does my code run?",
answer:
"On your machine. Repos, worktrees, terminal output, and agent sessions stay local by default; cloud sync covers account and organization metadata only. Superset doesn't proxy any API calls.",
},
{
question: "Do I need my own coding agent subscriptions?",
answer:
"Yes. Superset is the workspace your agents run in, not a model provider. Bring Claude Code, Codex, OpenCode, or any CLI agent, and use your existing accounts or API keys on every plan.",
},
];
10 changes: 10 additions & 0 deletions apps/marketing/src/app/pricing/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { COMPANY } from "@superset/shared/constants";
import type { Metadata } from "next";
import { FAQPageJsonLd } from "@/components/JsonLd";
import { ComparisonTable } from "./components/ComparisonTable";
import { PricingFAQ } from "./components/PricingFAQ";
import { PricingHero } from "./components/PricingHero";
import { PricingTiers } from "./components/PricingTiers";
import { TrustStrip } from "./components/TrustStrip";
import { PRICING_FAQ_ITEMS } from "./constants";

export const metadata: Metadata = {
title: "Pricing",
Expand All @@ -16,6 +19,7 @@ export const metadata: Metadata = {
export default function PricingPage() {
return (
<main className="relative min-h-screen">
<FAQPageJsonLd items={PRICING_FAQ_ITEMS} />
<PricingHero />

<section className="relative border-b border-border">
Expand All @@ -24,6 +28,12 @@ export default function PricingPage() {
</div>
</section>

<section className="relative border-b border-border">
<div className="max-w-6xl mx-auto px-6 py-10 md:py-12">
<TrustStrip />
</div>
</section>

<section className="relative border-b border-border">
<div className="max-w-6xl mx-auto px-6 py-12 md:py-16">
<ComparisonTable />
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/components/JsonLd/JsonLd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function SoftwareApplicationJsonLd() {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: COMPANY.NAME,
operatingSystem: "macOS, Windows, Linux",
operatingSystem: "macOS",
applicationCategory: "DeveloperApplication",
applicationSubCategory: "Developer Tools",
offers: {
Expand Down
Loading