Repository navigation
Add annual Pro pricing - #9234
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughPro billing now supports monthly and annual intervals across pricing pages, dashboard upsells, checkout URLs, Stripe metadata, analytics, and pricing display. Embedded app pages receive Ghostty-derived themes and generalized external-browser handling. ChangesPro billing intervals
Embedded web theme and navigation
Estimated code review effort: 5 (Critical) | ~90 minutes Sequence Diagram(s)sequenceDiagram
participant PricingPage
participant PricingIntervalProvider
participant PricingCheckoutButton
participant CheckoutRoute
participant Stripe
PricingPage->>PricingIntervalProvider: initialize selected interval
PricingPage->>PricingCheckoutButton: provide interval-specific checkout hrefs
PricingCheckoutButton->>CheckoutRoute: redirect with interval parameter
CheckoutRoute->>Stripe: create session with billingInterval metadata
Stripe-->>CheckoutRoute: return checkout session
Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (3 errors, 1 warning)
✅ Passed checks (21 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with 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.
Inline comments:
In `@web/app/`[locale]/dashboard/billing/page.tsx:
- Around line 524-530: Update priceCopy to derive the current monthly and yearly
display strings from PRO_PRICING_USD rather than hardcoding "$30/month" and
"$288/year"; preserve the existing lookup-key matching and legacy yearly
"$240/year" behavior.
In `@web/app/api/billing/checkout/route.ts`:
- Around line 103-108: Validate the raw interval query parameter before calling
proBillingInterval: allow an absent value to default to monthly, accept only
"month" or "year", and return the same invalid-checkout response used by
checkoutPlan for any explicit unrecognized value. Keep successUrl and cancelUrl
construction unchanged after validation.
In `@web/messages/en.json`:
- Around line 403-408: Add billingPeriod, monthly, annual, saveAnnual,
annualPriceDetail, and annualComparePrice to every locale file routed by
web/i18n/routing.ts, including all 18 listed non-English locales, using
appropriate translations or the established fallback convention. Keep the keys
consistent across locale files and preserve the existing English and Japanese
entries.
In `@web/tests/app-pricing-page.test.tsx`:
- Around line 113-117: Update the negative assertion in the pricing page test
around the checkout href expectation to check that the monthly interval
parameter is absent, rather than checking the unrelated appearance parameter.
Keep the existing yearly checkout assertion and active button assertion
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c79224da-5a34-44e9-93c2-2891c093ab67
📒 Files selected for processing (24)
skills/cmux-billing/SKILL.mdweb/app/[locale]/components/pro-cta-link.tsxweb/app/[locale]/dashboard/billing/page.tsxweb/app/[locale]/posthog.tsxweb/app/[locale]/pricing/page.tsxweb/app/api/billing/checkout/route.tsweb/app/app-pricing/page.tsxweb/app/components/checkout-navigation.tsxweb/app/components/pricing-interval-selector.tsxweb/app/components/pricing-shared.tsxweb/app/lib/billing.tsweb/app/lib/posthog-client.tsweb/messages/en.jsonweb/messages/ja.jsonweb/scripts/stripe/provision-live.shweb/services/billing/plans.tsweb/services/billing/stripe.tsweb/tests/app-pricing-page.test.tsxweb/tests/billing-checkout-route.test.tsweb/tests/billing-links.test.tsweb/tests/dashboard-billing-page.test.tsxweb/tests/pricing-page.test.tsxweb/tests/pro-cta-link.test.tsxweb/tests/pro-pricing.test.ts
|
Vercel preview: https://cmux-hkwtfh0zw-manaflow.vercel.app/pricing |
|
Updated preview: https://cmux-3sii2pcto-manaflow.vercel.app/pricing |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with 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.
Inline comments:
In `@Sources/Panels/BrowserNavigationDelegate.swift`:
- Around line 5-16: Update
BrowserExternalNavigationIntent.shouldOpenInSystemBrowser to honor
cmux_external_browser only when the URL’s scheme and host match the trusted web
origins resolved by AuthEnvironment for checkout, portal, or pricing URLs;
reject non-cmux hosts before evaluating the flag. Add a test covering a non-cmux
host with the flag and asserting it returns false, while preserving acceptance
for trusted origins.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b85939d2-2e98-48cf-b5cf-4865fb3c027a
📒 Files selected for processing (18)
Sources/Panels/BrowserNavigationDelegate.swiftSources/Panels/BrowserPanel.swiftSources/ProWelcomeChecklist.swiftcmuxTests/AuthEnvironmentTests.swiftweb/app/[locale]/dashboard/billing/page.tsxweb/app/[locale]/pricing/page.tsxweb/app/app-pricing/appearance.tsweb/app/app-pricing/page.tsxweb/app/app-pro-welcome/page.tsxweb/app/components/pricing-interval-selector.tsxweb/app/components/pricing-shared.tsxweb/app/lib/billing.tsweb/messages/en.jsonweb/messages/ja.jsonweb/tests/app-pricing-page.test.tsxweb/tests/app-pro-welcome-page.test.tsxweb/tests/dashboard-billing-page.test.tsxweb/tests/pricing-page.test.tsx
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with 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.
Inline comments:
In `@ghostty`:
- Line 1: Remove the invalid ghostty submodule pointer update and restore the
submodule to a reachable commit that exists in the Ghostty repository and is an
ancestor of its main branch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 213b1993-b5cc-4b3a-8485-8c49e7303b26
📒 Files selected for processing (1)
ghostty
|
Updated preview: https://cmux-hd36b5ntg-manaflow.vercel.app/pricing |
|
Current-head preview: https://cmux-kf0boh4yx-manaflow.vercel.app/pricing |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Current-head preview: https://cmux-6j97s7hzh-manaflow.vercel.app/pricing |
|
Live Stripe catalog verified: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 880a08d596
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (lookupKey === PRO_PRICING_USD.year.lookupKey) { | ||
| return `$${PRO_PRICING_USD.year.billedAmount}/year`; | ||
| } | ||
| return null; |
There was a problem hiding this comment.
Derive annual Pro copy from subscription metadata
When STRIPE_PRO_YEARLY_288_PRICE_ID references a valid operator-managed annual Price whose lookup key is absent or differs from cmux-pro-yearly-288, checkout still records billingInterval: "year", but this function falls through to null and StripePlan omits the price metric entirely. Use the persisted interval metadata as a fallback for new annual Pro subscriptions, while retaining the lookup-key check that distinguishes grandfathered $240/year subscriptions.
Useful? React with 👍 / 👎.
| #expect(script.contains("[data-cmux-app-theme]")) | ||
| #expect(script.contains("--ghostty-background")) | ||
| #expect(script.contains("--ghostty-foreground")) | ||
| #expect(script.contains("--cmux-product-blue")) | ||
| #expect(script.contains("--cmux-product-blue-on-background")) |
There was a problem hiding this comment.
Execute the generated theme script in a runtime harness
These assertions only search the generated JavaScript for variable names, so they still pass if the script is syntactically invalid, the selector no longer matches, or the assignments never affect the DOM. Execute the script against a small DOM/WebKit harness and assert the resulting styles and dataset instead; the repository's cmux-testing guidance requires runtime behavior rather than implementation-text checks.
AGENTS.md reference: AGENTS.md:L107-L107
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
3 issues found across 18 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/app/lib/billing.ts">
<violation number="1" location="web/app/lib/billing.ts:118">
P2: Cross-origin checkout can fail for fresh relay links under ordinary clock skew because the verifier has no future-time tolerance. Allow a small bounded clock-skew window (and apply it to both expiry bounds) or rely on the HMAC-authenticated expiry without rejecting valid tokens that are slightly future-dated.</violation>
<violation number="2" location="web/app/lib/billing.ts:218">
P2: Native callback relays can silently lose their callback when `cmuxScheme` contains casing or whitespace: the URL carries the normalized scheme, while the HMAC covers the raw scheme. Signing the same normalized `scheme` that is emitted would keep generation and verification consistent.</violation>
</file>
<file name="web/app/lib/native-callback.ts">
<violation number="1" location="web/app/lib/native-callback.ts:26">
P2: Tagged development-app checkouts completed through a deployed or cross-origin relay cannot finish native handoff: this function preserves `cmux-dev-*`, but `/handler/after-sign-in` later validates that scheme against its deployed request and rejects it as non-local. The trust established in Stripe metadata needs to be carried into the handoff validation (or the flow needs a signed server-side handoff) rather than only selecting the scheme here.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| return { | ||
| scheme, | ||
| expires, | ||
| signature: relaySignature(target, parameters, expires, secret), |
There was a problem hiding this comment.
P2: Native callback relays can silently lose their callback when cmuxScheme contains casing or whitespace: the URL carries the normalized scheme, while the HMAC covers the raw scheme. Signing the same normalized scheme that is emitted would keep generation and verification consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/lib/billing.ts, line 218:
<comment>Native callback relays can silently lose their callback when `cmuxScheme` contains casing or whitespace: the URL carries the normalized scheme, while the HMAC covers the raw scheme. Signing the same normalized `scheme` that is emitted would keep generation and verification consistent.</comment>
<file context>
@@ -140,3 +197,67 @@ function configuredAppPricingCheckoutURL(): URL | null {
+ return {
+ scheme,
+ expires,
+ signature: relaySignature(target, parameters, expires, secret),
+ };
+}
</file context>
| signature: relaySignature(target, parameters, expires, secret), | |
| signature: relaySignature( | |
| target, | |
| { ...parameters, cmuxScheme: scheme }, | |
| expires, | |
| secret, | |
| ), |
| ): string | null { | ||
| const scheme = rawScheme?.trim().toLowerCase() ?? ""; | ||
| if (NATIVE_SCHEMES.has(scheme) || scheme === "cmux-dev") return scheme; | ||
| return /^cmux-dev-[a-z0-9-]+$/.test(scheme) ? scheme : null; |
There was a problem hiding this comment.
P2: Tagged development-app checkouts completed through a deployed or cross-origin relay cannot finish native handoff: this function preserves cmux-dev-*, but /handler/after-sign-in later validates that scheme against its deployed request and rejects it as non-local. The trust established in Stripe metadata needs to be carried into the handoff validation (or the flow needs a signed server-side handoff) rather than only selecting the scheme here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/lib/native-callback.ts, line 26:
<comment>Tagged development-app checkouts completed through a deployed or cross-origin relay cannot finish native handoff: this function preserves `cmux-dev-*`, but `/handler/after-sign-in` later validates that scheme against its deployed request and rejects it as non-local. The trust established in Stripe metadata needs to be carried into the handoff validation (or the flow needs a signed server-side handoff) rather than only selecting the scheme here.</comment>
<file context>
@@ -18,6 +18,14 @@ export function validatedNativeCallbackScheme(
+): string | null {
+ const scheme = rawScheme?.trim().toLowerCase() ?? "";
+ if (NATIVE_SCHEMES.has(scheme) || scheme === "cmux-dev") return scheme;
+ return /^cmux-dev-[a-z0-9-]+$/.test(scheme) ? scheme : null;
+}
+
</file context>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Exact current-head Vercel preview for Verified with Vercel protection bypass: Annual selected, Save 20%, Pro $24/$288, Team $28/$336, and annual checkout links. |
|
Current head |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Final exact-head Vercel preview for Verified with Vercel protection bypass: exclusive interval control, Annual selected, Save 20%, explicit yearly billing cadence, Pro $24/$288, Team $28/$336, and annual checkout links. |
There was a problem hiding this comment.
3 issues found across 15 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/Panels/BrowserPanel.swift">
<violation number="1" location="Sources/Panels/BrowserPanel.swift:5278">
P2: Theme JavaScript is no longer origin-gated at the injection helper: any caller that passes an external `WKWebView` can mutate a page that presents `[data-cmux-app-theme]`. Keeping `supportsAppWebTheme(webView)` in this guard preserves the trusted-origin boundary at the `evaluateJavaScript` seam.</violation>
</file>
<file name="web/scripts/stripe/provision-catalog.sh">
<violation number="1" location="web/scripts/stripe/provision-catalog.sh:115">
P2: Catalog provisioning now scans inactive products too, so Stripe accounts with a large retired catalog can require many extra list requests before finding the canonical product. Passing the list endpoint's `active=true` filter would preserve the existing active-product behavior while retaining pagination.</violation>
</file>
<file name="web/app/components/pricing-interval-selector.tsx">
<violation number="1" location="web/app/components/pricing-interval-selector.tsx:102">
P2: Pricing interval now duplicates composite radio-group focus and selection behavior in this component, increasing the chance that keyboard and accessibility semantics drift from the shared UI implementation. Using Base UI's Radio Group (or native radio inputs if styling permits) would keep roving focus and checked-state behavior in a tested primitive.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| private func applyAppWebTheme(_ theme: AppWebThemeSnapshot, to webView: WKWebView) { | ||
| let browserTheme = theme.browserTheme | ||
| guard let script = browserTheme.applyingJavaScript() else { |
There was a problem hiding this comment.
P2: Theme JavaScript is no longer origin-gated at the injection helper: any caller that passes an external WKWebView can mutate a page that presents [data-cmux-app-theme]. Keeping supportsAppWebTheme(webView) in this guard preserves the trusted-origin boundary at the evaluateJavaScript seam.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Panels/BrowserPanel.swift, line 5278:
<comment>Theme JavaScript is no longer origin-gated at the injection helper: any caller that passes an external `WKWebView` can mutate a page that presents `[data-cmux-app-theme]`. Keeping `supportsAppWebTheme(webView)` in this guard preserves the trusted-origin boundary at the `evaluateJavaScript` seam.</comment>
<file context>
@@ -5275,11 +5275,7 @@ final class BrowserPanel: Panel, ObservableObject {
- trustedOrigin: AuthEnvironment.appWebOrigin
- ),
- let script = browserTheme.applyingJavaScript() else {
+ guard let script = browserTheme.applyingJavaScript() else {
return
}
</file context>
| guard let script = browserTheme.applyingJavaScript() else { | |
| guard supportsAppWebTheme(webView), | |
| let script = browserTheme.applyingJavaScript() else { |
| page_args+=(--data-urlencode "starting_after=${starting_after}") | ||
| fi | ||
| response="$( | ||
| stripe_get "/products" \ |
There was a problem hiding this comment.
P2: Catalog provisioning now scans inactive products too, so Stripe accounts with a large retired catalog can require many extra list requests before finding the canonical product. Passing the list endpoint's active=true filter would preserve the existing active-product behavior while retaining pagination.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/scripts/stripe/provision-catalog.sh, line 115:
<comment>Catalog provisioning now scans inactive products too, so Stripe accounts with a large retired catalog can require many extra list requests before finding the canonical product. Passing the list endpoint's `active=true` filter would preserve the existing active-product behavior while retaining pagination.</comment>
<file context>
@@ -101,19 +101,18 @@ product_matches_catalog_identity() {
response="$(
- stripe_get "/products/search" \
- --data-urlencode "query=name:'${name}' AND active:'true'" \
+ stripe_get "/products" \
--data-urlencode "limit=100" \
"${page_args[@]}"
</file context>
| stripe_get "/products" \ | |
| stripe_get "/products" \ | |
| --data-urlencode "active=true" \ |
| }, | ||
| [setInterval, surface], | ||
| ); | ||
| const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => { |
There was a problem hiding this comment.
P2: Pricing interval now duplicates composite radio-group focus and selection behavior in this component, increasing the chance that keyboard and accessibility semantics drift from the shared UI implementation. Using Base UI's Radio Group (or native radio inputs if styling permits) would keep roving focus and checked-state behavior in a tested primitive.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/components/pricing-interval-selector.tsx, line 102:
<comment>Pricing interval now duplicates composite radio-group focus and selection behavior in this component, increasing the chance that keyboard and accessibility semantics drift from the shared UI implementation. Using Base UI's Radio Group (or native radio inputs if styling permits) would keep roving focus and checked-state behavior in a tested primitive.</comment>
<file context>
@@ -84,37 +88,58 @@ export function PricingIntervalSelector({
+ },
+ [setInterval, surface],
+ );
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
+ let nextInterval: BillingInterval | null = null;
+ switch (event.key) {
</file context>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
There was a problem hiding this comment.
4 issues found across 20 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/scripts/stripe/provision-catalog.sh">
<violation number="1" location="web/scripts/stripe/provision-catalog.sh:106">
P3: This initialization is immediately overwritten at the top of the loop, so it never affects a request and makes the pagination state look initialized twice. Declaring the local array without an initial value keeps the loop as the single initialization point.</violation>
<violation number="2" location="web/scripts/stripe/provision-catalog.sh:244">
P3: This pre-loop assignment is dead setup because the loop overwrites it before the first webhook request. Removing it leaves behavior unchanged and avoids duplicate pagination initialization.</violation>
</file>
<file name="web/app/env.ts">
<violation number="1" location="web/app/env.ts:190">
P2: Retiring STRIPE_PRO_YEARLY_PRICE_ID via a hard validation error can take the whole production app down: in any non-preview runtime that still has the legacy var set (only `skipValidation` bypasses it), `env` throws 'Invalid environment variables' at boot, so the deployment crashes rather than degrading to the new key. Since the legacy key is explicitly retained for grandfathered subscriptions, consider logging a deprecation warning instead of a boot-failing error, or clearly sequencing env cleanup before deploying this version.</violation>
</file>
<file name="web/app/components/pricing-interval-selector.tsx">
<violation number="1" location="web/app/components/pricing-interval-selector.tsx:128">
P3: Screen-reader users are given a radiogroup with no reliable orientation even though the interval radios are laid out horizontally. Adding `aria-orientation="horizontal"` would expose the actual layout and align the group semantics with its left/right keyboard behavior.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| STRIPE_PRO_YEARLY_PRICE_ID: z.string().min(1).optional(), | ||
| // Deliberately distinct from the legacy STRIPE_PRO_YEARLY_PRICE_ID, | ||
| // which can refer to the grandfathered $240/year price. | ||
| STRIPE_PRO_YEARLY_PRICE_ID: retiredEnvValue( |
There was a problem hiding this comment.
P2: Retiring STRIPE_PRO_YEARLY_PRICE_ID via a hard validation error can take the whole production app down: in any non-preview runtime that still has the legacy var set (only skipValidation bypasses it), env throws 'Invalid environment variables' at boot, so the deployment crashes rather than degrading to the new key. Since the legacy key is explicitly retained for grandfathered subscriptions, consider logging a deprecation warning instead of a boot-failing error, or clearly sequencing env cleanup before deploying this version.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/env.ts, line 190:
<comment>Retiring STRIPE_PRO_YEARLY_PRICE_ID via a hard validation error can take the whole production app down: in any non-preview runtime that still has the legacy var set (only `skipValidation` bypasses it), `env` throws 'Invalid environment variables' at boot, so the deployment crashes rather than degrading to the new key. Since the legacy key is explicitly retained for grandfathered subscriptions, consider logging a deprecation warning instead of a boot-failing error, or clearly sequencing env cleanup before deploying this version.</comment>
<file context>
@@ -175,6 +187,10 @@ export const env = createEnv({
STRIPE_PRO_MONTHLY_PRICE_ID: z.string().min(1).optional(),
// Deliberately distinct from the legacy STRIPE_PRO_YEARLY_PRICE_ID,
// which can refer to the grandfathered $240/year price.
+ STRIPE_PRO_YEARLY_PRICE_ID: retiredEnvValue(
+ "STRIPE_PRO_YEARLY_PRICE_ID",
+ "STRIPE_PRO_YEARLY_288_PRICE_ID",
</file context>
| if [[ "$MODE" == "live" ]]; then | ||
| webhook_ids="" | ||
| starting_after="" | ||
| webhook_page_args=(--data-urlencode "limit=100") |
There was a problem hiding this comment.
P3: This pre-loop assignment is dead setup because the loop overwrites it before the first webhook request. Removing it leaves behavior unchanged and avoids duplicate pagination initialization.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/scripts/stripe/provision-catalog.sh, line 244:
<comment>This pre-loop assignment is dead setup because the loop overwrites it before the first webhook request. Removing it leaves behavior unchanged and avoids duplicate pagination initialization.</comment>
<file context>
@@ -247,15 +241,14 @@ ensure_price "$team_product_id" "cmux-team-yearly-336" "33600" "year" "cmux Team
if [[ "$MODE" == "live" ]]; then
webhook_ids=""
starting_after=""
+ webhook_page_args=(--data-urlencode "limit=100")
while :; do
- webhook_page_args=()
</file context>
| local plan="$2" | ||
| local response product_json product_id starting_after | ||
| local -a matching_product_ids=() | ||
| local -a page_args=(--data-urlencode "limit=100") |
There was a problem hiding this comment.
P3: This initialization is immediately overwritten at the top of the loop, so it never affects a request and makes the pagination state look initialized twice. Declaring the local array without an initial value keeps the loop as the single initialization point.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/scripts/stripe/provision-catalog.sh, line 106:
<comment>This initialization is immediately overwritten at the top of the loop, so it never affects a request and makes the pagination state look initialized twice. Declaring the local array without an initial value keeps the loop as the single initialization point.</comment>
<file context>
@@ -101,21 +101,18 @@ product_matches_catalog_identity() {
+ local response product_json product_id starting_after
local -a matching_product_ids=()
- local -a page_args=()
+ local -a page_args=(--data-urlencode "limit=100")
- next_page=""
</file context>
| local -a page_args=(--data-urlencode "limit=100") | |
| local -a page_args |
| <div | ||
| ref={captureView} | ||
| className="mx-auto mt-6 flex w-fit border border-border p-1 text-sm" | ||
| role="radiogroup" |
There was a problem hiding this comment.
P3: Screen-reader users are given a radiogroup with no reliable orientation even though the interval radios are laid out horizontally. Adding aria-orientation="horizontal" would expose the actual layout and align the group semantics with its left/right keyboard behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/components/pricing-interval-selector.tsx, line 128:
<comment>Screen-reader users are given a radiogroup with no reliable orientation even though the interval radios are laid out horizontally. Adding `aria-orientation="horizontal"` would expose the actual layout and align the group semantics with its left/right keyboard behavior.</comment>
<file context>
@@ -84,37 +88,58 @@ export function PricingIntervalSelector({
ref={captureView}
className="mx-auto mt-6 flex w-fit border border-border p-1 text-sm"
- role="group"
+ role="radiogroup"
aria-label={billingPeriodLabel}
+ onKeyDown={handleKeyDown}
</file context>
| role="radiogroup" | |
| role="radiogroup" | |
| aria-orientation="horizontal" |
There was a problem hiding this comment.
1 issue found across 20 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/scripts/stripe/provision-catalog.sh">
<violation number="1" location="web/scripts/stripe/provision-catalog.sh:106">
P3: The initial `page_args` value is never observed because every loop iteration overwrites it before the request is built. Removing this initializer and the analogous `webhook_page_args` initializer keeps pagination setup in one place and avoids dead setup that can drift from the loop's actual arguments.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| local plan="$2" | ||
| local response product_json product_id starting_after | ||
| local -a matching_product_ids=() | ||
| local -a page_args=(--data-urlencode "limit=100") |
There was a problem hiding this comment.
P3: The initial page_args value is never observed because every loop iteration overwrites it before the request is built. Removing this initializer and the analogous webhook_page_args initializer keeps pagination setup in one place and avoids dead setup that can drift from the loop's actual arguments.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/scripts/stripe/provision-catalog.sh, line 106:
<comment>The initial `page_args` value is never observed because every loop iteration overwrites it before the request is built. Removing this initializer and the analogous `webhook_page_args` initializer keeps pagination setup in one place and avoids dead setup that can drift from the loop's actual arguments.</comment>
<file context>
@@ -101,21 +101,18 @@ product_matches_catalog_identity() {
+ local response product_json product_id starting_after
local -a matching_product_ids=()
- local -a page_args=()
+ local -a page_args=(--data-urlencode "limit=100")
- next_page=""
</file context>
# Conflicts: # docs/ghostty-fork.md # ghostty # scripts/ghosttykit-checksums.txt
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ffcb8bc76b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// Creates a complete theme after the app has resolved readable accent variants. | ||
| public init( |
There was a problem hiding this comment.
Document the new public initializer parameters
For package consumers, this six-argument public initializer leaves the meanings and invariants of the three accent variants undocumented. The package API rules require - Parameter documentation for public initializers and documentation for public property invariants, so add DocC entries for these arguments rather than only a summary sentence.
AGENTS.md reference: AGENTS.md:L99-L102
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3afa9eb13a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| enterprise: pricing.enterprise.price, | ||
| }} | ||
| /> |
There was a problem hiding this comment.
Restore checkout actions in the embedded comparison
On /app-pricing, users who scroll to the comparison section no longer get its sticky Free, Pro, Team, or Enterprise actions because this PricingCompareTable call drops the entire actions prop; the public pricing page still wires interval-aware actions, and the pre-change embedded page supplied all four. Restore the actions using the shared checkout buttons while preserving the current-plan and App Store gating branches.
AGENTS.md reference: AGENTS.md:L89-L92
Useful? React with 👍 / 👎.
Summary
Validation
bun run test(931 passed, 128 skipped)bun run typecheckbun run buildann288, verified in the embedded Upgrade flowNeed help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Adds annual Pro ($288/year, $24/month) and Team ($336/year, $28/user/month) with a shared Monthly/Annual toggle across public, in‑app, and dashboard pricing. Secures checkout with an HMAC‑signed, same‑origin app relay and authenticated native return, and syncs the app theme to web surfaces with contrast‑safe product blue.
New Features
services/billing/plans; sharedPricingIntervalProvider/PricingIntervalSelectorandPricingCheckoutButton(toggle without navigation); shows monthly equivalents and savings; checkout links includeinterval; CTA/checkout analytics vialib/posthog-client.cmux-pro-yearly-288; Team yearlycmux-team-yearly-336; resolves month/year for both; envsSTRIPE_PRO_YEARLY_288_PRICE_ID,STRIPE_TEAM_YEARLY_PRICE_ID; unifiedweb/scripts/stripe/provision-catalog.shfortest/livewith authenticated retries.BrowserAppTheme; contrast‑adjusted--cmux-product-bluetokens for light/dark; theme injection pinned to the trusted origin; explicit external‑browser intent viacmux_external_browser=1from trusted sources; updatedghosttysubmodule and recorded GhosttyKit artifact for a working theme picker.Bug Fixes
interval; same‑origin, HMAC‑signed app checkout relay with expiry/signature; authenticate and preserve tagged native callback schemes through relay, completion, success, and after‑sign‑in;billingInvalidRelaybanner; guard Stripe pagination.next build; clean up embedded pricing layout; update macOS copy to $288/year.Written for commit 3afa9eb. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Tests