Skip to content

Unify pricing to Free & Pro (0/mo), Autumn billing, Convex cost controls, Kimi K2 models#93

Merged
Jackson57279 merged 3 commits intoopen-lovable-guifrom
capy/unify-pricing-to-20m-99074871
Sep 9, 2025
Merged

Unify pricing to Free & Pro (0/mo), Autumn billing, Convex cost controls, Kimi K2 models#93
Jackson57279 merged 3 commits intoopen-lovable-guifrom
capy/unify-pricing-to-20m-99074871

Conversation

@Jackson57279
Copy link
Owner

@Jackson57279 Jackson57279 commented Sep 9, 2025

Summary

  • Cohesive changes to pricing, billing, Convex usage on home, and reasoning model path.

Key changes

  1. Pricing + Autumn billing
  • Pricing page and modal simplified to two tiers: Free ($0) and Pro ($20/mo). Removed Enterprise/Team from UI.
  • components/stripe/SubscriptionPlans now only defines Free and Pro. Pro uses NEXT_PUBLIC_STRIPE_PRO_PRICE_ID when Stripe fallback is needed.
  • Autumn is the primary billing authority. Upgrade CTAs call /api/autumn/checkout. Billing portal via /api/autumn/billing-portal. Stripe routes left intact as fallback.
  • app/components/AutumnFallback synchronized to $0/$20 plans.
  • .env.example updated with NEXT_PUBLIC_STRIPE_PRO_PRICE_ID.
  1. Reduce Convex usage on the home page
  • Removed automatic updateChatScreenshot on sandbox creation.
  • Messages write only on Send.
  • Chat creation no longer auto-creates in the main page flow; users create via explicit New Chat button (ConvexChat already uses explicit createChat).
  • Polling remains light via usePolledQuery (60s). Added a manual Save preview button to store a screenshot explicitly.
  1. Model configuration: Kimi K2 for sequential-thinking
  • Replaced Groq-specific client/params in lib/groq-sequential-thinking.ts with provider-agnostic orchestration defaulting to appConfig.ai.defaultModel (moonshotai/kimi-k2-instruct-0905).
  • Removed Groq reasoning_format usage.
  1. Small UX polish for pricing
  • Clean two-card layout; concise features (Free: 5 chats, Pro: unlimited + advanced models). Consistent styling.

Autumn wrapper

  • Ensured a single AutumnWrapper is used in app/layout.tsx and re-export at project root for compatibility.

Acceptance notes

  • UI contains no Enterprise/Team pricing references. Docs may still mention Enterprise scenarios but are not user-visible UI.
  • Upgrade opens Autumn (with Stripe passthrough fallback). Billing portal goes through Autumn.
  • No Convex mutations occur on home initial load/idle (writes are gated by user actions).
  • Default sequential-thinking model is Kimi K2 and no direct @ai-sdk/groq usage remains in that module.

Security

  • AUTUMN_SECRET_KEY used server-side only; no client leakage.

Testing

  • Visit /pricing and test Upgrade. In-app pricing modal Upgrade triggers Autumn checkout.
  • On the home page, confirm no auto chat creation and no auto screenshot write; use Save preview button to persist a screenshot when desired.

₍ᐢ•(ܫ)•ᐢ₎ Generated by Capy (view task)

Summary by CodeRabbit

  • New Features

    • Add “Save preview” buttons to capture sandbox screenshots on demand.
    • Enable Pro checkout from Pricing and Chat, with automatic fallback for reliability.
    • Provide a billing portal endpoint for managing subscriptions.
  • Style

    • Simplify pricing UI and copy; unify tiers to Free and Pro ($20/month).
    • Update gating and labels to consistently reference Pro (Enterprise removed).
  • Refactor

    • Make sequential-thinking engine provider-agnostic and set default to Kimi K2.
  • Chores

    • Add public environment variable for Stripe Pro price ID.

…portal; consolidate Autumn wrapper; reduce Convex writes on home; default sequential thinking to Kimi K2; update env example
@vercel
Copy link

vercel bot commented Sep 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
zapdev Ready Ready Preview Comment Sep 9, 2025 3:17am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces Autumn-based checkout and billing-portal API routes with Stripe fallbacks, consolidates Autumn wrapper, updates pricing UIs to a Free/Pro model, adds a public Stripe Pro price ID env var, modifies home page to manual “Save preview,” and refactors sequential-thinking to a provider-agnostic engine with a minor interface extension.

Changes

Cohort / File(s) Summary
Billing/Checkout APIs
app/api/autumn/checkout/route.ts, app/api/autumn/billing-portal/route.ts
New POST routes authenticate via Clerk, attempt Autumn (checkout/portal) and fallback to internal Stripe endpoints; derive origin/returnUrl, handle errors, and return URL/session.
Pricing surfaces & CTA wiring
app/pricing/page.tsx, components/ConvexChat.tsx, app/components/AutumnFallback.tsx, components/EnhancedSettingsModal.tsx, app/test-pricing/page.tsx, components/MasterDashboard.tsx
Unified Free/Pro plans, simplified features/copy, removed Enterprise; replaced CheckoutButton with client flow calling /api/autumn/checkout; adjusted gating/messages; UI cleanups.
Autumn wrapper relocation
AutumnWrapper.tsx, .../app/components/AutumnWrapper
Local implementation removed; file now re-exports AutumnWrapper from ./app/components/AutumnWrapper.
Home page preview/save
app/page.tsx
Removed auto screenshot after sandbox creation; added manual savePreview function and buttons; gated message persistence on existing chatId; duplicate savePreview declarations present.
Environment variable
.env.example
Added NEXT_PUBLIC_STRIPE_PRO_PRICE_ID with comment; no removals.
Sequential thinking refactor
lib/groq-sequential-thinking.ts
Replaced Groq-specific logic with provider-agnostic flow (OpenAI/Anthropic/Gemini); integrated appConfig default model; adjusted progress events; added timestamp to ThoughtStep.
Meta
.capy_commit_msg
Added commit message helper file; no code changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User (Client)
  participant P as Next.js API\nPOST /api/autumn/checkout
  participant A as Autumn Service
  participant S as Stripe Service

  U->>P: POST { priceId? }
  alt AUTUMN_SECRET_KEY set
    P->>A: autumn.checkout(mockCtx, { priceId })
    alt Autumn returns URL
      A-->>P: { url | sessionUrl | redirectUrl }
      P-->>U: { url }
    else Autumn fails or no URL
      P->>S: POST /api/stripe/create-checkout-session { priceId|env }
      alt Stripe OK
        S-->>P: { sessionId }
        P-->>U: { provider: "stripe", sessionId }
      else Stripe error
        S-->>P: 4xx/5xx
        P-->>U: 500 error
      end
    end
  else No Autumn configured
    P->>S: POST /api/stripe/create-checkout-session
    S-->>P: { sessionId } or error
    P-->>U: sessionId or 500
  end
Loading
sequenceDiagram
  autonumber
  actor U as User (Client)
  participant P as Next.js API\nPOST /api/autumn/billing-portal
  participant A as Autumn Service
  participant S as Stripe Service

  U->>P: POST { returnUrl? } with auth
  alt Auth missing
    P-->>U: 401 { error: "Unauthorized" }
  else Auth OK
    alt AUTUMN_SECRET_KEY set
      P->>A: autumn.billingPortal(mockCtx, { returnUrl })
      alt Autumn returns URL
        A-->>P: { url | portalUrl | redirectUrl }
        P-->>U: { url }
      else Autumn fails
        P->>S: POST /api/stripe/customer-portal { returnUrl }
        S-->>P: { url } or error
        P-->>U: { url } or 500
      end
    else No Autumn configured
      P->>S: POST /api/stripe/customer-portal
      S-->>P: { url } or error
      P-->>U: { url } or 500
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

capy

Suggested reviewers

  • dogesman098

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately captures the PR’s primary changes by concisely listing the unified pricing tiers, Autumn billing integration, Convex write reduction, and switch to Kimi K2 models in a single sentence.
Description Check ✅ Passed The description clearly outlines the objectives and details of the PR, summarizing the pricing and billing updates, Convex usage controls, model configuration changes, and testing notes in alignment with the changeset.

Poem

A hop, a skip—two tiers we show,
Free fields bloom, Pro carrots glow. 🥕
Autumn winds guide checkout’s trail,
If leaves don’t turn, Stripe sets sail.
Thoughts align, providers sing—
Saved previews, screenshots take wing.
Thump-thump! Ship the spring.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
		  - name: "Undocumented Breaking Changes"
			  mode: "warning"
			  instructions: |
				  Flag potential breaking changes that are not documented:
				  1. Identify changes to public APIs/exports, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints (including removed/renamed items and changes to types, required params, return values, defaults, or behavior).
				  2. Ignore purely internal/private changes (e.g., code not exported from package entry points or marked internal).
				  3. Verify documentation exists: a "Breaking Change" section in the PR description and updates to CHANGELOG.md.

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch capy/unify-pricing-to-20m-99074871

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, please upgrade to Bugbot Pro by visiting the Cursor dashboard. Your first 14 days will be free!

…s; Autumn checkout on pricing page; fallback components normalized
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
components/MasterDashboard.tsx (1)

133-139: Make Monitoring gating consistent: Pro and Enterprise should see it.

Nav still restricts Monitoring to Enterprise, but content allows Pro. Unify to “non-free” across nav, content, and the restricted message.

   {
     id: 'monitoring',
     label: 'Monitoring',
     icon: Monitor,
     description: 'Real-time system monitoring',
-    available: userSubscription === 'enterprise'
+    available: userSubscription !== 'free'
   }
-                title={!isAvailable ? `${item.label} requires Pro subscription` : item.description}
+                title={!isAvailable ? `${item.label} requires Pro subscription` : item.description}

(Title can remain “Pro,” since gating becomes non-free.)

-{currentView === 'monitoring' && userSubscription !== 'free' && (
+{currentView === 'monitoring' && userSubscription !== 'free' && (
   <div className="text-center py-12">

(Already correct; kept for clarity.)

-{((currentView === 'autonomous' && userSubscription === 'free') ||
-  (currentView === 'analytics' && userSubscription === 'free') ||
-  (currentView === 'monitoring' && userSubscription !== 'enterprise')) && (
+{((currentView === 'autonomous' && userSubscription === 'free') ||
+  (currentView === 'analytics' && userSubscription === 'free') ||
+  (currentView === 'monitoring' && userSubscription === 'free')) && (
   <div className="text-center py-12">

Also applies to: 211-218, 412-422, 424-440

app/page.tsx (2)

1733-1748: Set currentChatId when a chat is selected to enable persistence and preview saving.

Without this, user messages aren’t saved and “Save preview” stays hidden.

-            <ConvexChat
-              onChatSelect={(chatId) => {
-                // Handle chat selection - could load chat context
-                console.log('Selected chat:', chatId);
-                // TODO: Load chat messages into current conversation context
-              }}
+            <ConvexChat
+              onChatSelect={(chatId) => {
+                setCurrentChatId(chatId);
+                console.log('Selected chat:', chatId);
+                // TODO: Load chat messages into current conversation context
+              }}

139-142: Fix race between sandbox creation and code application (stale sandboxData).

createSandbox completes before React state is observable within the same tick; processAIMessage may attempt to apply code with a null/old sandbox. Track the latest sandbox in a ref and consume it where needed.

Add a ref and keep it synced:

@@
-  const iframeRef = useRef<HTMLIFrameElement>(null);
+  const iframeRef = useRef<HTMLIFrameElement>(null);
   const chatMessagesRef = useRef<HTMLDivElement>(null);
   const codeDisplayRef = useRef<HTMLDivElement>(null);
+  const sandboxRef = useRef<SandboxData | null>(null);
+  useEffect(() => {
+    sandboxRef.current = sandboxData;
+  }, [sandboxData]);

Update on successful sandbox creation:

@@
-        setSandboxData(data);
+        setSandboxData(data);
+        sandboxRef.current = data;

Use the ref when sending apply requests:

@@
-          sandboxId: sandboxData?.sandboxId // Pass the sandbox ID to ensure proper connection
+          sandboxId: (sandboxRef.current ?? sandboxData)?.sandboxId

Use the ref for iframe refresh URLs:

@@
-            if (iframeRef.current && sandboxData?.url) {
+            const sd = sandboxRef.current ?? sandboxData;
+            if (iframeRef.current && sd?.url) {
               console.log('[home] Refreshing iframe after code application...');
-              const urlWithTimestamp = `${sandboxData.url}?t=${Date.now()}&applied=true`;
+              const urlWithTimestamp = `${sd.url}?t=${Date.now()}&applied=true`;
               iframeRef.current.src = urlWithTimestamp;
@@
-            if (iframeRef.current && sandboxData?.url) {
+            const sd = sandboxRef.current ?? sandboxData;
+            if (iframeRef.current && sd?.url) {
               console.log('[applyGeneratedCode] Starting iframe refresh sequence...');
-              console.log('[applyGeneratedCode] Current iframe src:', iframeRef.current.src);
-              console.log('[applyGeneratedCode] Sandbox URL:', sandboxData.url);
+              console.log('[applyGeneratedCode] Current iframe src:', iframeRef.current.src);
+              console.log('[applyGeneratedCode] Sandbox URL:', sd.url);
@@
-                const urlWithTimestamp = `${sandboxData.url}?t=${Date.now()}&force=true`;
+                const urlWithTimestamp = `${sd.url}?t=${Date.now()}&force=true`;
@@
-              newIframe.src = `${sandboxData.url}?t=${Date.now()}&recreated=true`;
+              newIframe.src = `${sd.url}?t=${Date.now()}&recreated=true`;

Optionally, also prefer the ref in other sandboxData?.url checks in this file to eliminate remaining edge cases.

Also applies to: 439-445, 548-553, 894-901, 920-943

components/ConvexChat.tsx (2)

138-149: Replace any with unknown and narrow error safely

Disallow any (C-10) and guard error.message access.

-    } catch (error: any) {
-      console.error('[ConvexChat] Failed to create chat:', error);
+    } catch (error: unknown) {
+      console.error('[ConvexChat] Failed to create chat:', error);
       // Provide specific error messages
-      if (error.message?.includes('User must be authenticated')) {
+      const msg = error instanceof Error ? error.message : String(error);
+      if (msg.includes('User must be authenticated')) {
         setError('Authentication failed. Please sign out and sign back in.');
-      } else if (error.message?.includes('ConvexError')) {
+      } else if (msg.includes('ConvexError')) {
         setError('Database connection failed. Check Convex configuration.');
       } else {
-        setError(`Failed to create chat: ${error.message || 'Unknown error'}`);
+        setError(`Failed to create chat: ${msg || 'Unknown error'}`);
       }

205-208: Disallow any in delete handler

Same rationale; narrow error.

-    } catch (error: any) {
-      console.error('Failed to delete chat:', error);
+    } catch (error: unknown) {
+      console.error('Failed to delete chat:', error);
lib/groq-sequential-thinking.ts (1)

16-25: Replace any[] with a minimal SearchSource type

Comply with C-10 and improve type safety for search results/sources.

 export interface ThoughtStep {
   stepNumber: number;
   thought: string;
   confidence: number;
   searchQueries?: string[];
-  searchResults?: any[];
+  searchResults?: SearchSource[];
   alternatives?: string[];
   reasoning: string;
   timestamp: number;
 }
 
 export interface UltraThinkResponse {
   reasoningPaths: ReasoningPath[];
   finalAnswer: string;
   confidence: number;
-  searchSources: any[];
+  searchSources: SearchSource[];
   processingTime: number;
   thoughtCount: number;
   ultraThinkMode: boolean;
 }
 
 export interface ReasoningProgress {
   type: 'thought_start' | 'thought_complete' | 'search_query' | 'search_results' | 'branch_created' | 'reasoning_complete';
   step?: ThoughtStep;
   pathId?: string;
   searchQuery?: string;
-  searchResults?: any[];
+  searchResults?: SearchSource[];
   progress?: number;
   message?: string;
 }

Add near the top of this file:

type SearchSource = {
  url?: string;
  title?: string;
  description?: string;
  [k: string]: unknown;
};

Also applies to: 49-56, 59-66

components/PricingModal.tsx (2)

46-52: Add dialog a11y: role, aria-modal, label; ensure button type

Improve screen-reader support and prevent accidental form submits.

-        <motion.div
+        <motion.div
           initial={{ scale: 0.9, opacity: 0 }}
           animate={{ scale: 1, opacity: 1 }}
           exit={{ scale: 0.9, opacity: 0 }}
-          className="bg-gray-900 rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto"
+          className="bg-gray-900 rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto"
+          role="dialog"
+          aria-modal="true"
+          aria-labelledby="pricing-modal-title"
           onClick={(e) => e.stopPropagation()}
         >
           <div className="p-6 border-b border-gray-700 flex items-center justify-between">
             <div>
-              <h2 className="text-2xl font-bold text-white">Upgrade Your Plan</h2>
+              <h2 id="pricing-modal-title" className="text-2xl font-bold text-white">Upgrade Your Plan</h2>
               <p className="text-gray-400 mt-1">You've reached the free plan limit of 5 chats.</p>
             </div>
-            <button
+            <button
+              type="button"
               onClick={onClose}
               className="text-gray-400 hover:text-white transition-colors"
               aria-label="Close"
             >

Also applies to: 55-56, 59-62


6-10: Tighten plan types: introduce PlanId and update props & handler signatures

  • In components/PricingModal.tsx, change onUpgrade to onUpgrade: (plan: PlanId) => Promise (with type PlanId = 'free' | 'pro').
  • In components/ConvexChat.tsx, update handleUpgrade to async (plan: PlanId) => Promise and branch on plan to select the correct priceId.
🧹 Nitpick comments (24)
.capy_commit_msg (1)

1-1: Clarify Pro price in the commit message.

To avoid ambiguity, call out Pro as $20/mo (since Free is shown as 0/mo). Example: “Unify pricing to Free ($0) and Pro ($20/mo) …”.

.env.example (1)

26-27: Fix dotenv key ordering to satisfy dotenv-linter.

Reorder the new key so it precedes NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as suggested by the linter.

Apply this diff in the Stripe section:

 NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_publishable_key_here
 STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key_here
-# Stripe Pro price ID (fallback when Autumn isn't configured)
-NEXT_PUBLIC_STRIPE_PRO_PRICE_ID=price_your_pro_monthly_price_id
+# Stripe Pro price ID (fallback when Autumn isn't configured)
+NEXT_PUBLIC_STRIPE_PRO_PRICE_ID=price_your_pro_monthly_price_id
components/stripe/SubscriptionPlans.tsx (1)

16-16: Use type-only React import for the icon type.

Avoid namespace React types and follow type-only import guidance.

-  icon?: React.ComponentType<{ className?: string }>;
+  import type { ComponentType } from 'react';
+  // ...
+  icon?: ComponentType<{ className?: string }>;
app/api/autumn/checkout/route.ts (2)

14-14: Avoid as any; type the Autumn ctx minimally.

Keeps strict mode clean per guidelines.

-    const mockCtx = { auth: { getUserIdentity: () => ({ subject: auth.userId, name: '', email: '' }) } } as any;
+    type AutumnAuthCtx = { auth: { getUserIdentity: () => { subject: string; name?: string; email?: string } } };
+    const mockCtx: AutumnAuthCtx = {
+      auth: { getUserIdentity: () => ({ subject: auth.userId, name: '', email: '' }) }
+    };

18-22: Type the Autumn result instead of any.

-        const result: any = await autumn.checkout(mockCtx, { priceId });
+        type AutumnCheckoutResult = { url?: string; sessionUrl?: string; redirectUrl?: string };
+        const result: AutumnCheckoutResult = await autumn.checkout(mockCtx, { priceId: resolvedPriceId });
app/api/autumn/billing-portal/route.ts (3)

17-21: Avoid as any and type the Autumn ctx/result.

-        const mockCtx = { auth: { getUserIdentity: () => ({ subject: auth.userId, name: '', email: '' }) } } as any;
-        const result: any = await autumn.billingPortal(mockCtx, { returnUrl: returnUrl || `${origin}/pricing` });
-        const url = result?.url || result?.portalUrl || result?.redirectUrl;
+        type AutumnAuthCtx = { auth: { getUserIdentity: () => { subject: string; name?: string; email?: string } } };
+        type AutumnPortalResult = { url?: string; portalUrl?: string; redirectUrl?: string };
+        const mockCtx: AutumnAuthCtx = {
+          auth: { getUserIdentity: () => ({ subject: auth.userId, name: '', email: '' }) }
+        };
+        const result: AutumnPortalResult = await autumn.billingPortal(mockCtx, { returnUrl: safeReturnUrl });
+        const url = result.url || result.portalUrl || result.redirectUrl;

28-33: Add timeout to Stripe fallback and use the sanitized return URL.

-    const stripeRes = await fetch(`${origin}/api/stripe/customer-portal`, {
+    const stripeRes = await fetch(`${origin}/api/stripe/customer-portal`, {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ returnUrl: returnUrl || `${origin}/pricing` })
+      body: JSON.stringify({ returnUrl: safeReturnUrl }),
+      signal: AbortSignal.timeout(10000)
     });

5-44: Deduplicate shared logic between Autumn routes.

Auth extraction, context creation, origin handling, and fallbacks are duplicated here and in checkout. Consider a tiny internal util under app/api/autumn/_utils.ts.

I can draft a minimal shared helper if you want.

app/page.tsx (1)

28-28: Use a type-only import for Id.

Aligns with coding guidelines and reduces runtime bundle.

-import { Id } from '@/convex/_generated/dataModel';
+import type { Id } from '@/convex/_generated/dataModel';
components/ConvexChat.tsx (4)

6-6: Use a type-only import for Id

Follow C-6 to avoid bundling type metadata into client code.

-import { Id } from '@/convex/_generated/dataModel';
+import type { Id } from '@/convex/_generated/dataModel';

45-46: Avoid logging PII in production

Email can leak in prod logs. Gate logs under development.

-    console.log('[ConvexChat] Status:', { isSignedIn, user: user?.emailAddresses?.[0]?.emailAddress, isConvexConfigured });
+    if (process.env.NODE_ENV === 'development') {
+      console.log('[ConvexChat] Status:', { isSignedIn, user: user?.emailAddresses?.[0]?.emailAddress, isConvexConfigured });
+    }

321-321: Prefer onKeyDown over onKeyPress

onKeyPress is deprecated; use onKeyDown with key === 'Enter'.

Also applies to: 464-464


211-230: Deduplicate checkout logic with pricing page

Same checkout flow exists in app/pricing/page.tsx. Extract a shared helper (O-1) to lib/autumn/checkout.ts and reuse in both spots.

app/pricing/page.tsx (1)

7-28: Extract shared checkout helper to lib to avoid duplication

startAutumnCheckout duplicates ConvexChat’s handleUpgrade. Extract to lib/autumn/checkout.ts and import in both files (O-1).

Proposed helper:

// lib/autumn/checkout.ts
export async function startCheckout(getStripe: () => Promise<any>, priceId?: string) {
  const pid = priceId ?? process.env.NEXT_PUBLIC_STRIPE_PRO_PRICE_ID;
  if (!pid) throw new Error('Missing priceId');
  const res = await fetch('/api/autumn/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ priceId: pid })
  });
  if (!res.ok) throw new Error(await res.text());
  const data = await res.json();
  if (data.url) return window.location.assign(data.url);
  if (data.provider === 'stripe' && data.sessionId) {
    const stripe = await getStripe();
    if (stripe) {
      const { error } = await stripe.redirectToCheckout({ sessionId: data.sessionId });
      if (error) throw error;
    }
  }
}
lib/groq-sequential-thinking.ts (3)

293-308: Fix technical term extraction: capitalized pattern never matches due to lowercasing

Use the original tokens for capitalization heuristics.

-  private generateSearchQueries(thought: string, originalQuery: string): string[] {
-    const queries: string[] = [];
-    const words = thought.toLowerCase().split(/\W+/).filter(word => word.length > 3);
-    const importantWords = words.filter(word => !['that', 'this', 'with', 'from', 'they', 'have', 'been', 'will', 'would', 'could', 'should'].includes(word));
+  private generateSearchQueries(thought: string, originalQuery: string): string[] {
+    const queries: string[] = [];
+    const wordsLower = thought.toLowerCase().split(/\W+/).filter(w => w.length > 3);
+    const importantWords = wordsLower.filter(w => !['that','this','with','from','they','have','been','will','would','could','should'].includes(w));
 
     if (importantWords.length >= 2) {
       queries.push(`${originalQuery} ${importantWords.slice(0, 3).join(' ')}`);
     }
 
-    const technicalTerms = words.filter(word => word.length > 5 && (word.includes('tion') || word.includes('ment') || word.includes('ness') || word.includes('able') || word.match(/^[A-Z][a-z]+[A-Z]/)));
+    const originalWords = thought.split(/\W+/).filter(Boolean);
+    const technicalTerms = originalWords.filter(word =>
+      word.length > 5 &&
+      (/tion|ment|ness|able/i.test(word) || /^[A-Z][a-z]+[A-Z]/.test(word))
+    );
     if (technicalTerms.length > 0) {
       queries.push(`${technicalTerms[0]} ${originalQuery.split(' ').slice(0, 2).join(' ')}`);
     }
 
     return queries.slice(0, 2);
   }

136-136: Avoid deprecated substr

Use slice for id generation.

-    const pathId = `path_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+    const pathId = `path_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;

355-360: Emit reasoning_complete after final answer generation

Signal completion for consumers of progress events.

   private async generateFinalAnswer(
@@
-    let finalAnswer = '';
+    let finalAnswer = '';
     for await (const textPart of result.textStream) {
       finalAnswer += textPart;
     }
 
-    return finalAnswer;
+    progressCallback?.({ type: 'reasoning_complete', message: 'Final answer generated.' });
+    return finalAnswer;

Also applies to: 394-400

components/PricingModal.tsx (7)

12-14: AnimatePresence exit animation is bypassed by early return

Returning null when isOpen is false prevents exit animations. Render conditionally inside AnimatePresence instead.

 export default function PricingModal({ isOpen, onClose, onUpgrade }: PricingModalProps) {
-  if (!isOpen) return null;
   ...
-  return (
-    <AnimatePresence>
-      <motion.div
+  return (
+    <AnimatePresence>
+      {isOpen && (
+      <motion.div
         initial={{ opacity: 0 }}
         animate={{ opacity: 1 }}
         exit={{ opacity: 0 }}
         className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
         onClick={onClose}
       >
         <motion.div
           initial={{ scale: 0.9, opacity: 0 }}
           animate={{ scale: 1, opacity: 1 }}
           exit={{ scale: 0.9, opacity: 0 }}
           className="bg-gray-900 rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto"
           onClick={(e) => e.stopPropagation()}
         >
           ...
         </motion.div>
       </motion.div>
+      )}
     </AnimatePresence>
   );
 }

15-24: De-dup the “5 chats” limit via a single constant

Avoid drift between header copy and features.

-  const plans: Plan[] = [
+  const FREE_CHAT_LIMIT = 5 as const;
+  const plans: Plan[] = [
     {
       id: 'free',
       name: 'Free',
       description: 'Perfect for getting started.',
       price: 0,
       features: [
-        '5 chats',
+        `${FREE_CHAT_LIMIT} chats`,
         'Basic models'
       ]
     },
-              <p className="text-gray-400 mt-1">You've reached the free plan limit of 5 chats.</p>
+              <p className="text-gray-400 mt-1">You've reached the free plan limit of {FREE_CHAT_LIMIT} chats.</p>

Also applies to: 55-57


26-33: Remove unused “popular” flag or style it

popular is set but not used. Either drop it or use it for a badge/variant.

-      popular: true,

81-84: Prefer locale-safe currency formatting

Safer for future currency changes and avoids hardcoding "$".

-                      <span className="text-3xl font-bold text-white">${plan.price}</span>
+                      <span className="text-3xl font-bold text-white">
+                        {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(plan.price)}
+                      </span>

86-94: Disable “Current Plan” button instead of closing modal

Avoids surprising close on Free; keep Pro as the only actionable CTA.

-                    <Button
-                      onClick={() => plan.id === 'pro' ? onUpgrade(plan.id) : onClose()}
+                    <Button
+                      disabled={plan.name === 'Free'}
+                      aria-disabled={plan.name === 'Free'}
+                      onClick={plan.name === 'Pro' ? () => onUpgrade('pro') : undefined}
                       className={`w-full ${
                         plan.name === 'Free'
-                          ? 'bg-green-600 hover:bg-green-700'
+                          ? 'bg-green-600 cursor-default opacity-60'
                           : 'bg-blue-600 hover:bg-blue-700'
                       }`}
                     >
                       {plan.name === 'Free' ? 'Current Plan' : 'Upgrade'}
                     </Button>

98-105: Use stable keys for features

Use the feature string as the key to avoid index-key pitfalls on reorders.

-                    {plan.features.map((feature, index) => (
-                      <div key={index} className="flex items-start gap-3">
+                    {plan.features.map((feature) => (
+                      <div key={feature} className="flex items-start gap-3">

39-45: Optional: support Escape to close and focus management

Consider adding an onKeyDown handler (with tabIndex={-1}) to close on Escape, and a focus trap to keep focus within the dialog while open. I can draft a minimal FocusTrap if helpful.

Also applies to: 46-51

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4184c12 and 922f86c.

📒 Files selected for processing (15)
  • .capy_commit_msg (1 hunks)
  • .env.example (1 hunks)
  • AutumnWrapper.tsx (1 hunks)
  • app/api/autumn/billing-portal/route.ts (1 hunks)
  • app/api/autumn/checkout/route.ts (1 hunks)
  • app/components/AutumnFallback.tsx (3 hunks)
  • app/page.tsx (4 hunks)
  • app/pricing/page.tsx (3 hunks)
  • app/test-pricing/page.tsx (8 hunks)
  • components/ConvexChat.tsx (1 hunks)
  • components/EnhancedSettingsModal.tsx (1 hunks)
  • components/MasterDashboard.tsx (3 hunks)
  • components/PricingModal.tsx (5 hunks)
  • components/stripe/SubscriptionPlans.tsx (6 hunks)
  • lib/groq-sequential-thinking.ts (9 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Disallow any types; use unknown, unions, or generics instead
Use branded types for IDs (e.g., type SandboxId = Brand<string, 'SandboxId'>) across the codebase
Use type-only imports: import type { ... } for types
Default to type over interface unless declaration merging is needed
Avoid comments except for critical caveats; write self-explanatory code
Prefer simple, composable functions over classes
Don't extract functions unless reused or for testability
Use branded types for external service IDs (SandboxId, ChatId, UserId) throughout the codebase

**/*.{ts,tsx}: C-1: Follow TDD (scaffold stub → write failing test → implement)
C-2: Name functions with established domain vocabulary for consistency
C-3: Avoid introducing classes when small testable functions suffice
C-4: Prefer simple, composable, testable functions
C-5: Prefer branded types for IDs (e.g., type SandboxId = Brand<string,'SandboxId'>)
C-6: Use import type { … } for type-only imports
C-7: Avoid comments except for critical caveats; rely on self-explanatory code
C-8: Default to type; use interface only when clearer or for interface merging
C-9: Don’t extract a new function unless reused, enables testing otherwise untestable logic, or drastically improves readability
C-10: Do not use any; use unknown, proper unions, or generics instead
D-4: Use branded types for external service IDs (E2B sandbox IDs, Convex document IDs)
G-2: TypeScript compilation must pass with strict mode
Writing Functions: Keep cyclomatic complexity low and code easy to follow
Writing Functions: Remove unused parameters
Writing Functions: Avoid unnecessary type casts; push casting to function boundaries
Writing Functions: Make functions easily testable without mocking core services; otherwise cover via integration tests
Writing Functions: Factor out hidden dependencies into arguments when they affect behavior
Writing Functions: Choose clear, consistent names; brainstorm alternatives
Writing Functions: Do n...

Files:

  • app/pricing/page.tsx
  • components/ConvexChat.tsx
  • AutumnWrapper.tsx
  • components/EnhancedSettingsModal.tsx
  • app/api/autumn/billing-portal/route.ts
  • app/api/autumn/checkout/route.ts
  • app/page.tsx
  • lib/groq-sequential-thinking.ts
  • app/test-pricing/page.tsx
  • components/MasterDashboard.tsx
  • components/PricingModal.tsx
  • components/stripe/SubscriptionPlans.tsx
  • app/components/AutumnFallback.tsx
app/api/**

📄 CodeRabbit inference engine (.cursorrules)

Group related routes in subdirectories

Files:

  • app/api/autumn/billing-portal/route.ts
  • app/api/autumn/checkout/route.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

O-2: Keep API route logic in app/api with clear separation between sandbox management, AI processing, and autonomous workflows

Files:

  • app/api/autumn/billing-portal/route.ts
  • app/api/autumn/checkout/route.ts
lib/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

O-1: Place shared utilities in lib/ only if used by two or more components or API routes

Files:

  • lib/groq-sequential-thinking.ts
🧠 Learnings (4)
📚 Learning: 2025-09-09T03:08:55.499Z
Learnt from: CR
PR: otdoges/zapdev#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-09T03:08:55.499Z
Learning: Applies to app/components/chat/**/*.{ts,tsx} : UI Components: app/components/chat contains chat and AI interaction components

Applied to files:

  • components/ConvexChat.tsx
📚 Learning: 2025-09-09T03:08:55.499Z
Learnt from: CR
PR: otdoges/zapdev#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-09T03:08:55.499Z
Learning: Applies to convex/chats.ts : Convex Schema: convex/chats.ts manages chat history and conversations

Applied to files:

  • app/page.tsx
📚 Learning: 2025-09-09T03:08:55.499Z
Learnt from: CR
PR: otdoges/zapdev#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-09T03:08:55.499Z
Learning: Applies to lib/ai/**/*.{ts,tsx} : Core Libraries: lib/ai contains multi-provider AI client wrappers and streaming utilities

Applied to files:

  • lib/groq-sequential-thinking.ts
📚 Learning: 2025-09-09T03:08:55.499Z
Learnt from: CR
PR: otdoges/zapdev#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-09T03:08:55.499Z
Learning: Applies to app/components/monitoring/**/*.{ts,tsx} : UI Components: app/components/monitoring contains dashboard and monitoring UI

Applied to files:

  • components/MasterDashboard.tsx
🧬 Code graph analysis (8)
app/pricing/page.tsx (2)
lib/stripe.ts (1)
  • stripe (7-9)
lib/stripe-client.ts (1)
  • getStripe (9-14)
components/ConvexChat.tsx (2)
lib/stripe.ts (1)
  • stripe (7-9)
lib/stripe-client.ts (1)
  • getStripe (9-14)
app/api/autumn/billing-portal/route.ts (1)
app/api/autumn/checkout/route.ts (1)
  • POST (5-49)
app/api/autumn/checkout/route.ts (1)
app/api/autumn/billing-portal/route.ts (1)
  • POST (5-45)
app/page.tsx (1)
convex/chats.ts (1)
  • updateChatScreenshot (325-362)
lib/groq-sequential-thinking.ts (3)
lib/mcp/sequential-thinking-service.ts (1)
  • getSequentialThinkingService (465-470)
lib/search/reasoning-search.ts (1)
  • getReasoningSearchService (496-501)
config/app.config.ts (1)
  • appConfig (4-177)
app/test-pricing/page.tsx (1)
components/stripe/CheckoutButton.tsx (1)
  • CheckoutButton (28-113)
components/PricingModal.tsx (1)
components/ui/button.tsx (1)
  • Button (52-52)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 27-27: [UnorderedKey] The NEXT_PUBLIC_STRIPE_PRO_PRICE_ID key should go before the NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY key

(UnorderedKey)

🔇 Additional comments (15)
AutumnWrapper.tsx (1)

2-2: No changes required; named export matches upstream
Confirmed that AutumnWrapper.tsx exports AutumnWrapper as a named function, so export { AutumnWrapper } from "./app/components/AutumnWrapper"; is correct.

components/stripe/SubscriptionPlans.tsx (5)

3-3: Icon import change is fine.

No issues with the reduced lucide-react imports.


35-37: Feature list trim looks good.

Copy aligned with Free plan constraints.


53-53: Copy change acknowledged.

“Advanced models” matches the PR scope.


74-74: Grid tweak OK.

Two-card layout matches “Free & Pro”.


153-153: No change needed – CheckoutButton expects amount in smallest currency unit (cents), so plan.price * 100 is correct.

app/api/autumn/checkout/route.ts (1)

44-46: Frontend covers both {url} and {provider, sessionId} shapes. ConvexChat and Pricing pages first check data.url (stripe API), then data.provider === 'stripe' && data.sessionId (autumn proxy), so no changes needed.

app/components/AutumnFallback.tsx (2)

75-107: Two-tier fallback pricing UX looks consistent with the new plans.

Copy, price ($20), features, and 2-column layout align with the PR objectives. No functional concerns here.


50-53: Confirm time unit for currentPeriodEnd.

If downstream expects Unix seconds (Stripe-style), this ms value will be off by 1000x. Verify consumers before shipping.

Would you like a quick scan to find usages of subscription.currentPeriodEnd and confirm the expected unit?

app/test-pricing/page.tsx (5)

23-49: Test plans updated to Free/Pro look good.

Env-driven Pro priceId with a safe fallback is a solid default; feature lists match the simplified offering.


136-148: Correct cents conversion for customAmount.

Rounding to integer cents avoids Stripe validation errors. LGTM.


165-191: Quick test scenarios updated.

Amounts and labels are coherent; good coverage of payment vs subscription paths.

Also applies to: 195-205


220-224: Test cards list is accurate and concise.

Covers success, decline, insufficient funds, and 3DS flows. Good for demoing error paths.


43-44: NEXT_PUBLIC_STRIPE_PRO_PRICE_ID is documented and wired. .env.example declares NEXT_PUBLIC_STRIPE_PRO_PRICE_ID, and all code references use that variable rather than falling back in production.

components/PricingModal.tsx (1)

112-114: Copy LGTM

“Cancel anytime.” is succinct and consistent with the simplified pricing narrative.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
app/components/AutumnFallback.tsx (6)

4-7: Prefer type-only imports and avoid React.FC per guidelines

Drop the default React import and use type-only imports; also prefer function components over React.FC.

-import React, { createContext, useContext, useState, useEffect } from 'react';
+import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';

Outside this hunk, update component declarations accordingly:

// before
export const AutumnProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { /*...*/ }
export const PricingTable: React.FC = () => { /*...*/ }

// after
export const AutumnProvider = ({ children }: { children: ReactNode }) => { /*...*/ }
export const PricingTable = () => { /*...*/ }

38-41: Remove unused state setter to satisfy no-unused-vars

setError is never used.

-  const [error, setError] = useState<string | null>(null);
+  const [error] = useState<string | null>(null);

82-87: Free plan copy/limits — confirm accuracy and simplify period

Ensure quotas match the new cost controls, and consider omitting the “period” label for Free to avoid odd “forever” wording.

-      period: 'forever',
+      period: undefined,

92-98: Pro plan copy: reflect Kimi K2 and pricing style

Match $20/mo wording and call out Kimi K2 explicitly.

-      period: 'per month',
-      description: 'For builders who want more',
-      features: ['Unlimited chats', 'Advanced models'],
+      period: '/mo',
+      description: 'For builders who want more',
+      features: ['Unlimited chats', 'Kimi K2 models'],

101-134: Wire the CTA to Autumn checkout (fallback should still upgrade)

Hook the Pro button to POST /api/autumn/checkout and redirect to the returned URL.

-            <Button className="w-full" variant={plan.popular ? 'default' : 'outline'}>
+            <Button
+              className="w-full"
+              variant={plan.popular ? 'default' : 'outline'}
+              onClick={plan.id === 'pro' ? () => startAutumnCheckout() : undefined}
+            >
               {plan.buttonText}
             </Button>

Add this helper near the top of the file (outside this hunk):

async function startAutumnCheckout() {
  try {
    const res = await fetch('/api/autumn/checkout', { method: 'POST' });
    const data = await res.json();
    if (data?.url) window.location.href = data.url;
  } catch {
    // noop fallback
  }
}

155-161: useUsageLimits: type mutate and align with branded IDs

  • Return a Promise from mutate to better mirror SWR-style signatures.
  • Consider branding featureId (e.g., type FeatureId = Brand<string, 'FeatureId'>) per guidelines.
   return {
     data: limits,
     isLoading,
-    mutate: () => {},
+    mutate: async () => {},
     isValidating: false
   };

If a shared Brand type exists in the repo, switch the hook signature to useUsageLimits(featureId: FeatureId).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 922f86c and 8753666.

📒 Files selected for processing (2)
  • app/components/AutumnFallback.tsx (3 hunks)
  • app/pricing/page.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/pricing/page.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Disallow any types; use unknown, unions, or generics instead
Use branded types for IDs (e.g., type SandboxId = Brand<string, 'SandboxId'>) across the codebase
Use type-only imports: import type { ... } for types
Default to type over interface unless declaration merging is needed
Avoid comments except for critical caveats; write self-explanatory code
Prefer simple, composable functions over classes
Don't extract functions unless reused or for testability
Use branded types for external service IDs (SandboxId, ChatId, UserId) throughout the codebase

**/*.{ts,tsx}: C-1: Follow TDD (scaffold stub → write failing test → implement)
C-2: Name functions with established domain vocabulary for consistency
C-3: Avoid introducing classes when small testable functions suffice
C-4: Prefer simple, composable, testable functions
C-5: Prefer branded types for IDs (e.g., type SandboxId = Brand<string,'SandboxId'>)
C-6: Use import type { … } for type-only imports
C-7: Avoid comments except for critical caveats; rely on self-explanatory code
C-8: Default to type; use interface only when clearer or for interface merging
C-9: Don’t extract a new function unless reused, enables testing otherwise untestable logic, or drastically improves readability
C-10: Do not use any; use unknown, proper unions, or generics instead
D-4: Use branded types for external service IDs (E2B sandbox IDs, Convex document IDs)
G-2: TypeScript compilation must pass with strict mode
Writing Functions: Keep cyclomatic complexity low and code easy to follow
Writing Functions: Remove unused parameters
Writing Functions: Avoid unnecessary type casts; push casting to function boundaries
Writing Functions: Make functions easily testable without mocking core services; otherwise cover via integration tests
Writing Functions: Factor out hidden dependencies into arguments when they affect behavior
Writing Functions: Choose clear, consistent names; brainstorm alternatives
Writing Functions: Do n...

Files:

  • app/components/AutumnFallback.tsx

@Jackson57279 Jackson57279 merged commit 4d65b5e into open-lovable-gui Sep 9, 2025
6 checks passed
@Jackson57279 Jackson57279 deleted the capy/unify-pricing-to-20m-99074871 branch September 9, 2025 22:53
@coderabbitai coderabbitai bot mentioned this pull request Nov 8, 2025
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant