Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@N2D4
Copy link
Contributor

@N2D4 N2D4 commented Jan 14, 2026

Note

Refactors payments to replace "catalogs" with "product lines" end-to-end, standardizing on productLines and productLineId.

  • Backend: swap catalogsproductLines and catalogIdproductLineId across seeds, APIs (/products, switch, purchase-session, validate-code), core logic (getSubscriptions, validatePurchaseSession, grants), and error messages; update tests accordingly
  • Dashboard: remove catalogs view; add product-lines pages/components and dialogs; update products UI, creation flows, filtering/grouping, and deep links to use product lines
  • Config: product lines now carry a customerType; UI enforces matching types; update defensive coding guidance and URL handling (urlString)
  • Misc: enable pnpm lint --fix in stop hook; minor README tip adjustments

Written by Cursor Bugbot for commit 5406496. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • Refactor
    • Renamed "Catalogs" to "Product Lines" throughout the payments UI, navigation, dialogs, labels, previews, and error messages.
  • New Features
    • Added a migration utility to convert legacy catalog-based payment configs to the new product-line format.
  • Chores
    • Switched defaults and config handling to productLines/productLineId while maintaining backward compatibility.
  • Tests
    • Added and updated end-to-end and unit tests to cover legacy compatibility and product-line behavior.
  • Documentation
    • Minor README and guidance updates; added defensive coding notes.

✏️ Tip: You can customize this high-level summary in your review settings.

@N2D4 N2D4 requested review from BilalG1 and Copilot January 14, 2026 22:39
@vercel
Copy link

vercel bot commented Jan 14, 2026

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

Project Deployment Review Updated (UTC)
stack-backend Ready Ready Preview, Comment Jan 16, 2026 9:16pm
stack-dashboard Ready Ready Preview, Comment Jan 16, 2026 9:16pm
stack-demo Ready Ready Preview, Comment Jan 16, 2026 9:16pm
stack-docs Ready Ready Preview, Comment Jan 16, 2026 9:16pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Caution

Review failed

The pull request is closed.

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

Walkthrough

Renames payment grouping from catalogsproductLines and field catalogIdproductLineId across backend, frontend, schema, seeds, migrations, and tests; adds migration utility migrateCatalogsToProductLines; updates UI/labels, validations, and E2E tests for backward compatibility.

Changes

Cohort / File(s) Summary
Backend seeds & fixtures
apps/backend/prisma/seed.ts, apps/backend/.../seedDummy*.ts
Seed and fixture data updated: catalogsproductLines and catalogIdproductLineId.
Backend API routes & switch logic
apps/backend/src/app/api/.../payments/products/[customer_type]/[customer_id]/route.ts, .../switch/route.ts, .../purchases/*/route.tsx, .../purchases/validate-code/route.ts
Controllers/validators now use productLineId as grouping key; maps/guards/error messages updated to product-line terminology; switch validation requires same productLineId.
Backend payments core & tests
apps/backend/src/lib/payments.tsx, apps/backend/src/lib/payments.test.tsx
Core logic and unit tests migrated to use productLineId/productLines; return shapes and variable names updated (validate/grant/getSubscriptions).
Schema, config & migration
packages/stack-shared/src/config/schema.ts, .../schema-fields.ts, .../schema-fuzzer.test.ts, packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
Public schema key renamed to productLines, added customerType, cross-field validation added, defaults updated, and new migration utility migrateCatalogsToProductLines (with tests).
Dashboard — product management UI
apps/dashboard/.../payments/products/* (e.g., product-dialog.tsx, page-client-*, create-product-line-dialog.tsx, new/page-client.tsx, list views)
UI, props and handlers refactored: catalogproduct line, catalogIdproductLineId; added create/update/delete product-line dialogs; grouping, previews and code-generation updated.
Dashboard — product-lines pages & nav
apps/dashboard/src/app/.../payments/product-lines/*, apps/dashboard/src/lib/apps-frontend.tsx
Pages, metadata, nav labels and hrefs updated from “Catalogs” → “Product Lines”.
Dashboard removed module
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/catalogs/layout.tsx
Deleted CatalogsLayout (Stripe onboarding/payments setup UI) file.
E2E tests
apps/e2e/tests/backend/endpoints/api/v1/payments/** (multiple files)
New/updated E2E tests asserting backward compatibility for legacy catalogs/catalogId configs; expectations updated to product-line wording.
Config/type usages & helpers
apps/dashboard/src/components/data-table/payment-product-table.tsx, packages/stack-shared/src/schema-fields.ts
Types/schema updated: removed catalogId, added productLineId; BranchPayments type alias introduced.
Template, docs & misc
packages/template/.../payments-panel.tsx, AGENTS.md, README.md, .cursor/hooks/stop-check.sh
Minor copy/text updates (“catalog” → “product line”), added AGENTS guidance, README snippet tweak, and stop-check lint now runs --fix.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API as "Payments API"
  participant Service as "Payments Service"
  participant Config as "Config / Migration"
  participant DB

  Client->>API: request purchase / validate-code / switch
  API->>Service: validate request (expects productLineId)
  Service->>Config: load payments config
  alt legacy config present
    Config->>Config: migrateCatalogsToProductLines()\n(convert catalogs→productLines, catalogId→productLineId)
    Config-->>Service: productLines + inferred customerType
  else already migrated
    Config-->>Service: productLines
  end
  Service->>DB: read/write subscriptions & products (productLineId keys)
  DB-->>Service: subscription/product data
  Service-->>API: response (errors/messages reference "product line")
  API-->>Client: return result / redirect URL
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • BilalG1

Poem

🐇 I hopped from catalog fields to product-line vine,
Swapped IDs and labels, kept every flow fine.
Seeds, schema, UI, tests — each line did align.
Migrate the configs, run tests in a line.
Carrots ready — product lines now shine! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Rename catalog to product line' accurately describes the main change in the pull request, which is a comprehensive refactor of the payments domain terminology.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all major changes across backend, config, tests, and dashboard UI with clear bullet points and organization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR renames the "catalog" terminology to "product line" throughout the codebase to better reflect the business concept. The changes include schema updates, backend logic, API endpoints, frontend UI, and comprehensive test coverage.

Changes:

  • Renamed catalogs/catalogId to productLines/productLineId across schema, backend, and frontend
  • Added customerType field to product lines with validation ensuring products match their product line's customer type
  • Included backward compatibility migration logic for old config properties
  • Updated all UI components, error messages, and documentation to use "product line" terminology

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/stack-shared/src/schema-fields.ts Updated product schema to use productLineId instead of catalogId
packages/stack-shared/src/config/schema.ts Renamed config schema from catalogs to productLines, added customerType field and validation, added migration functions
packages/stack-shared/src/utils/jwt.tsx Added debug console.log statement (unrelated to PR)
packages/stack-shared/src/config/schema-fuzzer.test.ts Updated test fuzzer config for new schema
apps/e2e/tests/backend/endpoints/api/v1/payments/*.test.ts Updated E2E tests to use new terminology
apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/*.test.ts Added backward compatibility tests
apps/backend/src/lib/payments.tsx Updated all payment logic to use productLineId instead of catalogId
apps/backend/src/lib/payments.test.tsx Updated unit tests
apps/backend/src/app/api/latest/payments/**/*.ts Updated API endpoints to use new terminology
apps/backend/prisma/seed.ts Updated seed data
apps/dashboard/src/lib/apps-frontend.tsx Updated navigation to "Product Lines"
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/**/*.tsx Renamed all UI components and dialogs from catalog to product line
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/catalogs/layout.tsx Deleted old catalogs layout file
Comments suppressed due to low confidence (4)

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:2448

  • The onCreateProductLine function only sets displayName but the schema now requires customerType for product lines. This will cause validation errors. The function should pass customerType from the newProductLineCustomerType state variable. Update line 2446 to: { displayName, customerType: newProductLineCustomerType }
    apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:2161
  • Button text should have proper spacing: 'Create Product Line' instead of 'Create ProductLine'.
    apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:1504
  • The function signature for onCreateProductLine is missing the customerType parameter, which is now required by the schema. Update the signature to: onCreateProductLine: (productLineId: string, displayName: string, customerType: 'user' | 'team' | 'custom') => Promise<void>
    apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:2139
  • The call to onCreateProductLine is missing the required customerType argument. Update to: await onCreateProductLine(id, displayName, newProductLineCustomerType);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 14, 2026

Greptile Summary

This PR renames "catalog" to "product line" throughout the codebase, introducing a clearer naming convention for the mutual exclusivity grouping concept in the payments system. The refactoring includes schema updates, API logic changes, UI component updates, and comprehensive backward compatibility migrations.

Key Changes:

  • Config schema: catalogsproductLines, catalogIdproductLineId
  • Product lines now require explicit customerType field (was optional before)
  • Dashboard routes updated: /catalogs/product-lines, with catalog layout merged into parent
  • Payment validation logic updated to enforce product line mutual exclusivity
  • 407 lines of backward compatibility tests added
  • Automatic config migration via migrateConfigOverride() for old deployments

Critical Issues Found:

  1. Config Migration Bug: The migration renames catalogs to productLines but doesn't infer the new required customerType field from products. Old configs will fail validation after migration because product line entries will have customerType: undefined.
  2. Debug Code: Console.log statement left in JWT verification function leaks sensitive options

Test Coverage: Comprehensive - old config formats are tested to verify they work after migration, though the migration bug may cause those tests to fail in practice.

Confidence Score: 2/5

  • This PR has critical issues that must be resolved before merging: a config migration bug affecting backward compatibility and debug code in production
  • Score of 2 reflects critical issues found: (1) The config migration doesn't infer customerType for product lines, causing old configs to fail schema validation after migration - this breaks backward compatibility promises and will cause failures for existing deployments; (2) A debug console.log statement in the JWT utility leaks sensitive verification options. While most of the refactoring is well-executed with good test coverage, these two issues prevent safe merging. The first issue is particularly critical as it affects the core backwards compatibility mechanism.
  • packages/stack-shared/src/config/schema.ts (critical - migration logic), packages/stack-shared/src/utils/jwt.tsx (debug statement removal)

Important Files Changed

Filename Overview
packages/stack-shared/src/config/schema.ts Critical migration bug: Product line migration doesn't infer customerType from products, causing old configs to fail validation when missing the required customerType field that's now mandatory
packages/stack-shared/src/utils/jwt.tsx Debug console.log statement left in JWT verification function leaks potentially sensitive options to console
packages/stack-shared/src/schema-fields.ts Schema field changes correctly renamed catalogId to productLineId with updated OpenAPI descriptions
apps/backend/src/lib/payments.tsx Payment business logic correctly updated to use productLineId and handle product line mutual exclusivity constraints
apps/backend/src/lib/payments.test.tsx All 134 test cases properly updated with new productLineId field and added customerType to product line definitions

Sequence Diagram

sequenceDiagram
    participant Customer
    participant App as App Client
    participant API as Backend API
    participant Config as Config System
    participant Stripe

    Customer->>App: Select product
    App->>API: POST /create-purchase-url
    API->>Config: Load payments config
    Config->>Config: Migrate old catalogs→productLines
    Config->>Config: Validate productLineId.customerType matches product.customerType
    API->>API: getSubscriptions()<br/>Check for product line conflicts
    API->>API: validatePurchaseSession()<br/>Ensure mutual exclusivity
    API->>Stripe: Create/update subscription
    Stripe-->>API: subscription_id
    API-->>App: Purchase code
    App-->>Customer: Payment link
    
    Note over Config: ISSUE: Migration doesn't<br/>infer customerType,<br/>old configs will fail validation
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

30 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

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: 4

Caution

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

⚠️ Outside diff range comments (4)
apps/backend/src/lib/payments.test.tsx (2)

1206-1206: Update error message assertion to match production code.

The test expects "Multiple include-by-default products configured in the same catalog" but the production code throws "Multiple include-by-default products configured in the same product line". Change catalog to product line in the assertion on line 1206.


879-879: Update error message assertions to match production code.

Two test assertions use incorrect error message strings:

  • Line 879: expects "...in this product catalog" but production code throws "...in this product line"
  • Line 1206: expects "...in the same catalog" but production code throws "...in the same product line"

Change both occurrences from "product catalog" to "product line" to match the actual error messages in payments.tsx.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

329-358: Replace try/catch + destructive toast with runAsynchronouslyWithAlert (and avoid toast for blocking errors).
The catch block uses a destructive toast (Line 354-355). This is a blocking failure (product wasn’t created) and should be an alert / runAsynchronouslyWithAlert path per guidelines.

Proposed refactor (keep your loading state; let the alert wrapper surface errors)
-  const handleSave = async () => {
+  const handleSave = () => {
     const validationErrors = validateForm();
     if (Object.keys(validationErrors).length > 0) {
       setErrors(validationErrors);
       return;
     }
 
-    setIsSaving(true);
-    try {
+    runAsynchronouslyWithAlert(async () => {
+      setIsSaving(true);
+      try {
         const product: Product = {
           displayName,
           customerType,
           productLineId: productLineId || undefined,
           isAddOnTo: isAddOn ? Object.fromEntries(isAddOnTo.map(id => [id, true])) : false,
           stackable,
           prices: freeByDefault ? 'include-by-default' : prices,
           includedItems,
           serverOnly,
           freeTrial,
         };
 
         await project.updateConfig({ [`payments.products.${productId}`]: product });
         toast({ title: "Product created" });
         router.push(`/projects/${projectId}/payments/products`);
-    } catch (error) {
-      toast({ title: "Failed to create product", variant: "destructive" });
-    } finally {
-      setIsSaving(false);
-    }
+      } finally {
+        setIsSaving(false);
+      }
+    });
   };
Based on learnings and as per coding guidelines, prefer `runAsynchronouslyWithAlert` for async submit handlers and avoid error toasts.

Also applies to: 341-357

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

2044-2166: Replace blocking validation error toasts with alerts (and consider loading state).
Creating a product line can fail validation (invalid ID / duplicate ID), and those failures block the action. Using toast(..., variant: "destructive") here is easy to miss.

As per coding guidelines, blocking alerts/errors shouldn’t use toast. Also, this is an async handler without a loading/disabled state; consider disabling the button while the request is in-flight.

🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx:
- Around line 564-565: Replace the internal camelCase “productLine” with
user-facing “product line” in the UI copy: update the DialogDescription text
(and the other corresponding user-facing strings near the same block) so phrases
like “in the same productLine” and “same customer type and productLine” read “in
the same product line” and “same customer type and product line”; locate these
strings in the page-client.tsx DialogDescription/related JSX and change only the
displayed text.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx:
- Around line 762-767: existingProductsList currently maps over
paymentsConfig.products assuming each product exists, which can crash if some
entries are null/undefined; update the creation of existingProductsList to
mirror the falsy-product guard used for groupedProducts by filtering
Object.entries(paymentsConfig.products) to only include entries where product is
truthy (or by conditionally mapping and skipping nulls) before constructing
objects with id, displayName, productLineId, and customerType so no access on
null/undefined occurs.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:
- Around line 2437-2443: The onSaveProductWithGroup handler is overwriting the
whole payments.productLines.{productLineId} with an empty object which wipes
properties like displayName created by onCreateProductLine; update
onSaveProductWithGroup to stop setting
[`payments.productLines.${productLineId}`] to {} (remove that key from the
update payload) or instead set it to preserve the existing displayName (e.g.,
[`payments.productLines.${productLineId}`]: { displayName: existingDisplayName
}) while still writing [`payments.products.${productId}`]: product and calling
toast.

In `@packages/stack-shared/src/utils/jwt.tsx`:
- Line 66: Remove the leftover debug log "console.log({ options })" from
packages/stack-shared/src/utils/jwt.tsx so the raw JWT/token in the options
object is not emitted to logs; search the jwt utility for any other ad-hoc
console.log/debug prints and delete them or replace with safe, non-sensitive
logging via the existing logger if needed, then run tests/lint to ensure no
unrelated changes were introduced.
🧹 Nitpick comments (9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx (1)

97-97: Align hover transition with coding guidelines and file consistency.

The className uses transition-colors without hover:transition-none, which creates both hover-enter and hover-exit transitions. Per coding guidelines, hover transitions should only occur on hover-exit. The form variant in this same file (lines 163, 239) correctly implements this pattern with transition-colors duration-150 hover:transition-none.

♻️ Proposed fix to align with hover transition pattern
-                className="px-3 py-3 hover:bg-muted/50 flex items-center justify-between transition-colors"
+                className="px-3 py-3 hover:bg-muted/50 flex items-center justify-between transition-colors duration-150 hover:transition-none"

As per coding guidelines: "When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions".

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)

8-14: Consider using sanitizeUserSpecifiedId for consistency.

The toIdFormat helper lowercases the input and strips non-alphanumeric characters, but sanitizeUserSpecifiedId (used on line 116 for manual edits) preserves case. This creates inconsistent behavior:

  • Auto-generated ID: "Pricing Tiers""pricing-tiers" (lowercase)
  • Manual edit: "Pricing-Tiers""Pricing-Tiers" (case preserved)

Consider applying .toLowerCase() to the manual edit path as well, or documenting this as intentional behavior.

♻️ Optional: Apply consistent lowercase transformation
             onChange={(e) => {
-              const value = sanitizeUserSpecifiedId(e.target.value);
+              const value = sanitizeUserSpecifiedId(e.target.value).toLowerCase();
               setProductLineId(value);
               setHasManuallyEditedId(true);
               setErrors(prev => ({ ...prev, id: undefined }));
             }}
apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts (1)

1260-1260: Test title still references "catalog" instead of "product line".

The test title says "same catalog" but should say "same product line" to be consistent with the rename throughout the codebase.

Suggested fix
-it("should immediately cancel existing subscriptions when granting a product of same catalog", async ({ expect }) => {
+it("should immediately cancel existing subscriptions when granting a product of same product line", async ({ expect }) => {
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (1)

670-670: Minor: transition class may not work as expected.

The transition-colors class is applied but there's no hover state on this div. The opacity transition on the buttons inside (line 678) uses group-hover, but the parent div doesn't have the group class.

Suggested fix
                          <div
                            key={itemId}
-                            className="px-3 py-3 hover:bg-muted/50 flex items-center justify-between transition-colors"
+                            className="px-3 py-3 hover:bg-muted/50 flex items-center justify-between transition-colors group"
                          >
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx (1)

633-645: Minor: consider normalizing/typing productLineId and avoiding ! where easy.
If product.productLineId can ever be null/"" (or product can be falsy), normalizing to undefined (and/or tightening types for groups / paymentsGroups) will reduce edge-case drift. As per coding guidelines, prefer defensive patterns over non-null assertions when reasonable.

Also applies to: 680-685, 708-710

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

314-320: Product line + add-on selection should be constrained by customerType (and ideally auto-filtered).
Right now, product lines are reset on customer type change (Line 280-282), but the Select still lists all product lines (Line 915-919). Also, the add-on parent picker isn’t filtered by customer type, only validated by productLine (Line 314-319). This can let users pick invalid combinations that later UIs prevent.

As per coding guidelines, prefer preventing invalid user choices over surfacing toasts/errors later.

Also applies to: 895-925

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (3)

60-66: toIdFormat helper is fine, but it’s duplicated across multiple files.
Consider centralizing to avoid subtle divergence over time (you now have at least 3 copies).


2167-2204: Async edit/delete product line flows: consider error surfacing + loading.
These handlers call async callbacks and then toast success, but failures will currently surface poorly (or via a global boundary).

As per coding guidelines, prefer runAsynchronouslyWithAlert (or equivalent) for async button actions that can fail.

Also applies to: 2206-2225


2455-2478: Deletion-by-move looks correct; confirm undefined is the right “unset” value for persisted config.
You set productLineId: undefined when moving products to “No product line”. If the config layer expects null to clear fields (as in other parts of the codebase), you may want to normalize to null or omit the key entirely. The inline comment explaining the as any is good.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba38f26 and d41b807.

📒 Files selected for processing (30)
  • apps/backend/prisma/seed.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
  • apps/backend/src/lib/payments.test.tsx
  • apps/backend/src/lib/payments.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/catalogs/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • packages/stack-shared/src/config/schema.ts
  • packages/stack-shared/src/schema-fields.ts
  • packages/stack-shared/src/utils/jwt.tsx
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
💤 Files with no reviewable changes (1)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/catalogs/layout.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • packages/stack-shared/src/config/schema.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/backend/src/lib/payments.test.tsx
  • apps/backend/src/lib/payments.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • packages/stack-shared/src/schema-fields.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • packages/stack-shared/src/utils/jwt.tsx
  • apps/backend/prisma/seed.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • packages/stack-shared/src/config/schema.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/backend/src/lib/payments.test.tsx
  • apps/backend/src/lib/payments.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • packages/stack-shared/src/schema-fields.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • packages/stack-shared/src/utils/jwt.tsx
  • apps/backend/prisma/seed.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • packages/stack-shared/src/config/schema.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/backend/src/lib/payments.test.tsx
  • apps/backend/src/lib/payments.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts
  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • packages/stack-shared/src/schema-fields.ts
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
  • packages/stack-shared/src/utils/jwt.tsx
  • apps/backend/prisma/seed.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
{**/apps-frontend.tsx,**/apps-config.ts}

📄 CodeRabbit inference engine (AGENTS.md)

To update the list of available apps, edit apps-frontend.tsx and apps-config.ts. Check existing apps for inspiration when implementing new apps or pages

Files:

  • apps/dashboard/src/lib/apps-frontend.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry
Use .toMatchInlineSnapshot over other selectors in tests when possible, and check/modify snapshot-serializer.ts to understand how snapshots are formatted

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/backend/src/lib/payments.test.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
packages/stack-shared/src/config/schema.ts

📄 CodeRabbit inference engine (AGENTS.md)

Whenever making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts

Files:

  • packages/stack-shared/src/config/schema.ts
🧠 Learnings (5)
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to {**/apps-frontend.tsx,**/apps-config.ts} : To update the list of available apps, edit `apps-frontend.tsx` and `apps-config.ts`. Check existing apps for inspiration when implementing new apps or pages

Applied to files:

  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • apps/backend/src/lib/payments.test.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • packages/stack-shared/src/config/schema.ts
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • packages/stack-shared/src/schema-fields.ts
  • apps/backend/prisma/seed.ts
📚 Learning: 2026-01-11T06:44:57.542Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 1069
File: packages/template/src/components-page/onboarding.tsx:59-61
Timestamp: 2026-01-11T06:44:57.542Z
Learning: In the stack-auth codebase, Button components from stackframe/stack-ui (including those used via MessageCard props like primaryAction and secondaryAction) can accept async functions directly and automatically handle loading states with indicators. No need to wrap these with runAsynchronouslyWithAlert.

Applied to files:

  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • packages/template/src/components-page/account-settings/payments/payments-panel.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
🧬 Code graph analysis (10)
apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts (1)
apps/backend/src/lib/payments.tsx (1)
  • isActiveSubscription (308-310)
apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx (1)
apps/backend/src/lib/payments.tsx (1)
  • validatePurchaseSession (598-682)
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts (1)
packages/stack-shared/src/utils/errors.tsx (1)
  • StatusError (152-261)
apps/backend/src/lib/payments.tsx (2)
packages/stack-shared/src/utils/objects.tsx (2)
  • typedEntries (263-265)
  • typedKeys (304-306)
packages/stack-shared/src/schema-fields.ts (1)
  • productSchema (624-647)
apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts (1)
apps/e2e/tests/helpers.ts (1)
  • it (12-12)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (2)
packages/stack-shared/src/schema-fields.ts (3)
  • isValidUserSpecifiedId (453-455)
  • getUserSpecifiedIdErrorMessage (460-462)
  • sanitizeUserSpecifiedId (469-477)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts (1)
  • Product (9-9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (8)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts (1)
  • Product (9-9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useProjectId (46-53)
packages/stack-shared/src/utils/strings.tsx (1)
  • stringCompare (61-65)
packages/stack-shared/src/schema-fields.ts (2)
  • isValidUserSpecifiedId (453-455)
  • getUserSpecifiedIdErrorMessage (460-462)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx (1)
  • PageClient (68-108)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (1)
  • PageClient (72-95)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx (1)
  • PageClient (598-944)
packages/stack-shared/src/utils/objects.tsx (2)
  • typedEntries (263-265)
  • typedFromEntries (281-283)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: all-good
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
🔇 Additional comments (68)
packages/template/src/components-page/account-settings/payments/payments-panel.tsx (2)

5-8: LGTM!

The Result import is properly added and correctly used with Result.fromThrowingAsync() on line 389 for error handling in the plan-switching flow. Import ordering is clean.


380-380: LGTM!

The terminology update from "catalog" to "product line" aligns with the PR objective. The text is properly internationalized via t().

packages/stack-shared/src/schema-fields.ts (1)

624-647: Schema field rename looks correct and is properly supported by migrations.

The productLineId field definition follows the established pattern with proper validation via userSpecifiedIdSchema and includes a clear OpenAPI description explaining the mutual exclusivity semantics.

The migration function at lines 380-382 of packages/stack-shared/src/config/schema.ts correctly handles the backwards-incompatible catalogIdproductLineId rename for existing data, using the renameProperty utility to update the config path.

apps/dashboard/src/lib/apps-frontend.tsx (1)

150-167: LGTM! Navigation and description updates are consistent with the route structure.

The changes correctly update both the navigation item (displayName to "Product Lines" and href to "./product-lines") and the store description text. The corresponding product-lines directory exists in the route structure, and no remaining "catalogs" references were found in the payments section.

packages/stack-shared/src/config/schema-fuzzer.test.ts (2)

67-82: LGTM! Good backward compatibility testing for migration paths.

The fuzzer config correctly includes both the new productLines structure with customerType and the legacy catalogs/groups properties to ensure migration functions work correctly. This is well-aligned with the schema migration strategy.


96-98: Migration coverage is appropriate.

Including catalogId and groupId alongside productLineId in the fuzzer ensures backward compatibility during the migration period.

apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts (1)

73-91: LGTM! Product line conflict detection logic is correct.

The refactored code properly:

  1. Looks up product lines from the tenancy config
  2. Filters conflicts by matching productLineId
  3. Preserves the API response contract with conflicting_products

The guard at line 76 (if (productLineId)) correctly handles the case when a product has no product line assigned.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts (2)

1-63: Excellent backward compatibility test coverage.

This test correctly validates that the migration layer properly handles legacy catalogs and catalogId config properties. The test structure is clear with good comments explaining the purpose.

Based on learnings, adding E2E tests for API changes is critical in this industry, and this test appropriately covers the backward compatibility path.


65-167: Comprehensive conflict detection test with legacy config.

The test properly validates the complete flow:

  1. Setup with legacy catalogs/catalogId properties
  2. Purchase one product
  3. Validate that the second product correctly reports conflicts

This ensures the migration layer maintains functional correctness for conflict detection.

apps/backend/src/lib/payments.test.tsx (3)

61-62: LGTM! Manual change tests updated correctly.

The test fixtures properly use productLines: {} for scenarios without product line grouping.

Also applies to: 104-106


146-159: LGTM! Subscription tests properly updated.

The test fixtures correctly use productLines in the tenancy config and productLineId in product definitions.


913-914: LGTM! Response property assertions updated correctly.

The tests properly verify the renamed response properties:

  • res.productLineId (was res.catalogId)
  • res.conflictingProductLineSubscriptions (was res.conflictingCatalogSubscriptions)

Also applies to: 951-952, 1064-1065

apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts (2)

64-66: LGTM! Product line validation correctly updated.

The validation properly checks productLineId and the error message is updated to use "product line" terminology consistently.


93-99: LGTM! One-time purchase check updated consistently.

The variable rename to hasOneTimeInProductLine and the updated error message maintain consistency with the broader nomenclature change.

apps/backend/prisma/seed.ts (3)

115-119: Consistent rename from catalogs to productLines in internal project config.

The payments configuration correctly migrates from catalogs to productLines terminology, with all product definitions updated to use productLineId instead of catalogId. The structure and semantics remain unchanged.

Also applies to: 122-122, 143-143, 164-164, 179-179


817-817: Dummy payments setup correctly updated for productLine terminology.

The buildDummyPaymentsSetup function properly renames:

  • Product definitions now use productLineId (e.g., 'workspace', 'add_ons')
  • Top-level config key renamed from catalogs to productLines

Consistent with the broader refactor.

Also applies to: 845-845, 881-881, 908-915


1234-1239: Inline product seed data updated to use productLineId.

The inline product definitions in seedDummyTransactions (legacy enterprise pilot and design audit pass) correctly use productLineId instead of catalogId.

Also applies to: 1409-1418

apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts (2)

49-70: Switch options grouping correctly migrated to productLineId.

The Map-based grouping logic properly renamed from switchOptionsByCatalogId to switchOptionsByProductLineId. The guard on line 55 and subsequent Map operations consistently use product.productLineId. Logic remains functionally identical.


76-80: Product mapping correctly references productLineId for switch options.

The final product mapping extracts productLineId and uses it to look up switch options from the grouped Map. Consistent with the upstream changes.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (2)

22-56: Component and state management correctly updated for Product Line.

The component properly:

  • Renames from CreateCatalogDialog to CreateProductLineDialog
  • Updates onCreate callback signature to use productLine object
  • Adds hasManuallyEditedId state for tracking user intent
  • Validates using productLineId with appropriate error messages

Form reset logic correctly clears all new state fields.


66-142: UI terminology and auto-ID generation properly implemented.

The dialog correctly:

  • Updates all labels and descriptions to "Product Line" terminology
  • Auto-generates ID from display name when user hasn't manually edited
  • Uses runAsynchronously for async onCreate handler per coding guidelines
apps/backend/src/lib/payments.tsx (5)

86-88: Inline product correctly returns productLineId: undefined.

When creating an inline product (not from config), productLineId is correctly set to undefined since inline products don't belong to any product line.


318-375: Subscription retrieval logic correctly migrated to productLines.

The getSubscriptions function properly:

  • Reads from tenancy.config.payments.productLines
  • Tracks which product lines have DB subscriptions via productLinesWithDbSubscriptions Set
  • Iterates product lines to find and add default "include-by-default" products
  • Maintains correct error message for multiple defaults in the same product line (line 356)

Logic remains functionally identical to the previous catalog-based implementation.


377-393: Ungrouped default detection correctly checks productLineId === undefined.

Products without a productLineId that are "include-by-default" are correctly identified and added as default subscriptions.


609-681: validatePurchaseSession return type and conflict detection updated.

The function correctly:

  • Returns productLineId and conflictingProductLineSubscriptions instead of catalog equivalents
  • Looks up product line from tenancy.config.payments.productLines
  • Blocks purchases when one-time purchase exists in same product line
  • Filters conflicting subscriptions based on product.productLineId

All terminology and logic consistent with the rename.


721-755: grantProductToCustomer correctly uses renamed conflict subscriptions.

The destructuring and conflict handling logic properly uses conflictingProductLineSubscriptions to cancel conflicting subscriptions in the same product line.

apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx (1)

61-73: Purchase session handler correctly uses renamed conflict subscriptions.

The destructuring and conflict handling logic properly uses conflictingProductLineSubscriptions from the updated validatePurchaseSession return type. Logic remains unchanged.

apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts (1)

1270-1316: LGTM!

The product configuration correctly uses the new productLines and productLineId properties. The test validates the expected behavior of products within the same product line.

packages/stack-shared/src/config/schema.ts (4)

151-157: LGTM!

The schema correctly renames catalogs to productLines and catalogId to productLineId. The addition of customerType to product lines with corresponding cross-field validation is a good enhancement.


169-191: LGTM!

The cross-field validation ensures data integrity by:

  1. Verifying that referenced product lines exist
  2. Enforcing that product customerType matches its product line's customerType

The error messages are descriptive and helpful for debugging.


374-384: LGTM!

The migration functions correctly handle the backward-incompatible schema changes:

  1. Renames payments.catalogs to payments.productLines
  2. Renames payments.products.*.catalogId to payments.products.*.productLineId

This follows the established migration pattern and ensures existing configurations continue to work. Based on learnings, this is the required approach for backwards-incompatible changes to the config schema.


585-591: LGTM!

The default values are correctly updated to use the new productLines and productLineId naming.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--purchase-session.test.ts (3)

1-60: LGTM! Excellent backward compatibility coverage.

This test correctly validates that the old catalogs and catalogId configuration properties continue to work after the rename. The migration functions in schema.ts should automatically convert these to productLines and productLineId.


62-130: LGTM!

This test validates that:

  1. Old catalogs/catalogId config is properly migrated
  2. Product line grouping logic works correctly after migration
  3. Error messages use the new "product line" terminology

132-240: LGTM!

The subscription switching test validates that the group-based switching logic continues to work correctly with the old configuration format after migration.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (1)

880-880: LGTM!

The updated assertion correctly expects the new "product line" terminology in the error message, even when the test uses the old configuration format. This validates that the migration layer works correctly end-to-end.

apps/e2e/tests/backend/endpoints/api/v1/payments/switch-plans.test.ts (3)

4-13: LGTM!

The setupProducts function signature and implementation are correctly updated to use productLines instead of catalogs.


16-63: LGTM!

The test is correctly updated:

  • Title reflects new "product line" terminology
  • Configuration uses productLineId instead of catalogId
  • Expected error message uses new terminology

65-106: LGTM!

The test correctly uses productLineId for both products, maintaining consistency with the rename.

apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts (1)

157-176: LGTM! Consistent rename from catalog to product line terminology.

The test configuration correctly updates catalogsproductLines and catalogIdproductLineId for both productA and productB. Test logic and assertions remain unchanged.

apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (5)

169-189: LGTM! Product line terminology correctly applied to group subscription test.

The test configuration properly uses productLines and productLineId for testing one-time purchases with conflicting group subscriptions.


641-675: LGTM! Consistent rename in subscription switching test.

The product configuration correctly uses productLines and productLineId for both productA and productB when testing subscription updates within a group.


748-781: LGTM! DB-only subscription cancellation test updated correctly.

Configuration and product definitions properly reflect the product line terminology.


911-932: LGTM! One-time purchase blocking test updated.

The test correctly uses productLines and productLineId for testing purchase blocking within the same group.


969-969: LGTM! Error message assertion updated to reflect product line terminology.

The assertion correctly checks for the updated error message containing "one-time purchase in this product line".

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (7)

9-9: LGTM! Import updated to match renamed dialog component.


25-26: LGTM! Props interface updated with product line terminology.

Both existingProducts item type and existingProductLines prop correctly reflect the rename.


65-65: LGTM! State variables renamed consistently.

productLineId and showProductLineDialog states properly renamed from their catalog equivalents.

Also applies to: 76-76


135-140: LGTM! Validation logic correctly updated for product line consistency.

The add-on validation properly checks that all selected products share the same productLineId and shows an appropriate error message.


466-499: LGTM! UI labels and selection logic updated for product lines.

The select component correctly uses "Product Line" terminology in labels, placeholders, and options.


568-572: LGTM! Add-on product display shows product line name.

The display logic correctly looks up product line names from existingProductLines.


770-778: LGTM! CreateProductLineDialog integration updated correctly.

The dialog integration properly uses the renamed component and callback.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page.tsx (1)

3-5: LGTM! Page metadata title updated to reflect product line terminology.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/product-lines/page-client.tsx (3)

9-9: LGTM! Import updated to renamed view component.


17-17: LGTM! WelcomeScreen title and description updated with product line terminology.

The descriptions clearly explain the product line concept and its relationship to products.

Also applies to: 51-56


100-105: LGTM! PageLayout and view component properly renamed.

The title, description, and component reference consistently use product line terminology.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx (2)

357-370: Product grouping/rendering rename looks consistent (productLineId end-to-end).
The filter + render paths now consistently key by productLineId and fall back to “Other” for ungrouped.

Also applies to: 388-436


818-829: Wiring paymentsConfig.productLines + existingProductLines into child components looks correct.
Prop renames are consistent and the page now passes product-line groups to both desktop and mobile layouts.

Also applies to: 880-890, 913-927

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (2)

193-285: Resetting productLineId on customer type change is a good guardrail.
This keeps productLine selection consistent with the “product lines are customer-type-specific” constraint.


287-292: No changes required—the code pattern is correct. The CreateProductLineDialog's onCreate contract explicitly accepts (productLine: {...}) => void | Promise<void>, and handleCreateProductLine correctly returns void. The dialog wraps the result with runAsynchronously(), which handles both void and Promise<void> return types uniformly via its signature promiseOrFunc: void | Promise<unknown> | (...) | undefined. This pattern is intentional and working as designed.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (3)

105-107: productLineName resolution + header display look good.
The fallback to ID when displayName is missing is consistent and keeps the header stable.

Also applies to: 216-221


259-283: Product line selection/create flow is coherent (incl. __none__ sentinel).
Nice touch using __none__ to avoid empty string Select values; handlers correctly map it to null in config updates.

Also applies to: 338-357, 417-429, 627-632


202-204: No action needed: route is already correct.

The actual code at line 202 uses /payments/product-lines#product-${productId} (with hyphens), which matches the existing directory structure. The review comment's snippet is inaccurate; the navigation route is not broken.

Likely an incorrect or invalid review comment.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (5)

571-573: ProductLine metadata propagation (prompt + add-on candidate filtering) looks correct.
This keeps generated docs and add-on constraints aligned with the productLine model.

Also applies to: 850-852


1512-1689: Regrouping by (productLineId, customerType) is a solid model for “type-specific” product lines.
The keying and sorting logic is clear and should keep the UI deterministic across customer types.


1716-1753: Edit/delete affordances for product lines are implemented cleanly (hover-revealed actions).
The empty product line rendering + controls is also a nice completeness touch.

Also applies to: 1990-2016


2386-2418: Double-check semantics: deleting the last product also deletes its product line.
Given this file now explicitly supports empty product lines (including an empty-product-line section + edit/delete), auto-deleting the product line on last product deletion may be surprising. If intentional, it might be worth a comment in code / UI copy explaining it.


2502-2505: existingProductLines mapping into ProductDialog looks consistent with the new naming.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 2

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

331-360: Replace manual try-catch with runAsynchronouslyWithAlert for consistent error handling.

The handleSave function uses a try-catch block with toast for error notification. As per coding guidelines, you should never try-catch-all and never use toast for blocking alerts and errors. Instead, use runAsynchronouslyWithAlert which provides consistent error handling.

♻️ Suggested refactor
  const handleSave = async () => {
    const validationErrors = validateForm();
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }

    setIsSaving(true);
-   try {
+   await runAsynchronouslyWithAlert(async () => {
      const product: Product = {
        displayName,
        customerType,
        productLineId: productLineId || undefined,
        isAddOnTo: isAddOn ? Object.fromEntries(isAddOnTo.map(id => [id, true])) : false,
        stackable,
        prices: freeByDefault ? 'include-by-default' : prices,
        includedItems,
        serverOnly,
        freeTrial,
      };

      await project.updateConfig({ [`payments.products.${productId}`]: product });
      toast({ title: "Product created" });
      router.push(`/projects/${projectId}/payments/products`);
-   } catch (error) {
-     toast({ title: "Failed to create product", variant: "destructive" });
-   } finally {
-     setIsSaving(false);
-   }
+   }).finally(() => {
+     setIsSaving(false);
+   });
  };
🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx:
- Around line 202-203: Change the menu item label text to use a human-readable
form: update the DropdownMenuItem that calls
router.push(`/projects/${projectId}/payments/product-lines#product-${productId}`)
so its display string is "View in Product Lines" (two words) instead of "View in
ProductLines"; keep the router.push call and URL unchanged and only modify the
visible text inside the DropdownMenuItem component.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:
- Line 2162: Replace the inconsistent label "Create ProductLine" with "Create
Product Line" in the component that renders the create button in
page-client-product-lines-view (search for the literal "Create ProductLine" in
page-client-product-lines-view.tsx and update the JSX/label or translation key
used by the button to the spaced form); if the label comes from a translation
key, update the corresponding copy/value instead of the inline string so it
remains consistent across the UI.
♻️ Duplicate comments (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (2)

2019-2021: Query parameter productLineId may not be read by the target page.

This link passes productLineId as a query parameter, but based on prior review comments, the receiving page (/payments/products/new/page-client.tsx) may not be reading this parameter. Please verify the target page uses useSearchParams to read and apply this value for pre-selecting the product line.

#!/bin/bash
# Check if the new product page reads the productLineId query parameter
rg -n "productLineId|useSearchParams" "apps/dashboard/src/app/(main)/(protected)/projects/\[projectId\]/payments/products/new/" -A 3 -B 1

2438-2443: Product line displayName will be wiped when saving product with group.

As noted in a prior review, the override function replaces the entire object at a given key path rather than merging. Setting payments.productLines.${productLineId} to {} will wipe the displayName property that was set during product line creation.

Either remove this line entirely if the product line already exists, or preserve the displayName:

Proposed fix
         onSaveProductWithGroup={async (productLineId, productId, product) => {
+          // Get existing productLine data to preserve displayName
+          const existingProductLine = paymentsConfig.productLines[productLineId];
           await project.updateConfig({
-            [`payments.productLines.${productLineId}`]: {},
+            [`payments.productLines.${productLineId}`]: existingProductLine ?? {},
             [`payments.products.${productId}`]: product,
           });
           toast({ title: "Product created" });
         }}

Or if the product line should already exist before this is called, simply remove the productLines update:

         onSaveProductWithGroup={async (productLineId, productId, product) => {
           await project.updateConfig({
-            [`payments.productLines.${productLineId}`]: {},
             [`payments.products.${productId}`]: product,
           });
           toast({ title: "Product created" });
         }}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (2)

564-565: Minor UI copy: Use "product line" (two words) in dialog description.

The user-facing text should read "in the same product line" instead of "in the same productLine".


589-589: Minor UI copy: Use "product line" (two words) in empty state message.

Should read "same customer type and product line" instead of "same customer type and productLine".

🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

382-384: Navigation change from router to window.history.back() is intentional but loses type safety.

Using window.history.back() means the user could navigate to an external page if they entered this page directly. Consider using the router with a fallback:

💡 Optional: Add fallback for direct page access
  const handleCancel = () => {
-   window.history.back();
+   if (window.history.length > 1) {
+     window.history.back();
+   } else {
+     router.push(`/projects/${projectId}/payments/products`);
+   }
  };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d41b807 and 653fa44.

📒 Files selected for processing (5)
  • AGENTS.md
  • apps/backend/src/lib/payments.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry
Use .toMatchInlineSnapshot over other selectors in tests when possible, and check/modify snapshot-serializer.ts to understand how snapshots are formatted

Files:

  • apps/backend/src/lib/payments.test.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/payments.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/backend/src/lib/payments.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/backend/src/lib/payments.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
🧠 Learnings (7)
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry

Applied to files:

  • apps/backend/src/lib/payments.test.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to {**/apps-frontend.tsx,**/apps-config.ts} : To update the list of available apps, edit `apps-frontend.tsx` and `apps-config.ts`. Check existing apps for inspiration when implementing new apps or pages

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Code defensively using `?? throwErr(...)` instead of non-null assertions, with good error messages explicitly stating violated assumptions

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use ES6 maps instead of records wherever possible

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use `Date.now()` for measuring elapsed (real) time; instead use `performance.now()`

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Try to avoid the `any` type. When using `any`, leave a comment explaining why and how the type system fails or how errors would still be caught

Applied to files:

  • AGENTS.md
🧬 Code graph analysis (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts (1)
  • Product (9-9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (6)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts (1)
  • Product (9-9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useProjectId (46-53)
packages/stack-shared/src/utils/strings.tsx (1)
  • stringCompare (61-65)
packages/stack-shared/src/utils/urls.tsx (1)
  • urlString (314-316)
packages/stack-shared/src/schema-fields.ts (2)
  • isValidUserSpecifiedId (453-455)
  • getUserSpecifiedIdErrorMessage (460-462)
packages/stack-shared/src/utils/objects.tsx (2)
  • typedEntries (263-265)
  • typedFromEntries (281-283)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (28)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (4)

289-294: LGTM!

The handleCreateProductLine function correctly uses runAsynchronouslyWithAlert for async error handling, and the rename from handleCreateCatalog to handleCreateProductLine with the updated config path payments.productLines.{id} is consistent with the PR objectives.


315-321: LGTM!

The validation logic correctly checks that all selected add-on products belong to the same product line, with the error message properly updated to reference "product line" instead of "catalog".


897-927: LGTM!

The product line dropdown is correctly implemented with:

  • Proper state binding to productLineId
  • Sentinel value 'no-product-line' for the empty state
  • Iteration over paymentsConfig.productLines
  • Create new option triggering the dialog

The rename from catalog terminology is complete and consistent.


991-995: LGTM!

The CreateProductLineDialog component is correctly wired up with the renamed state and handler props, consistent with the external snippet showing the dialog's expected interface.

apps/backend/src/lib/payments.test.tsx (6)

61-62: Consistent terminology update across test fixtures.

The rename from catalogsproductLines and catalogIdproductLineId is applied consistently across the manual changes and subscription test cases. The mock tenancy configurations correctly use the new property names.

Also applies to: 105-106, 150-153, 188-191, 227-230


913-914: Property accessors correctly updated.

The assertions accessing res.productLineId and res.conflictingProductLineSubscriptions reflect the renamed return type properties from validatePurchaseSession.

Also applies to: 951-952, 1064-1065


817-846: Test correctly validates duplicate one-time purchase blocking.

The mock data at line 820 correctly uses productLineId: undefined for the existing purchase, and the codeData.product at line 834 also uses productLineId: undefined. The test logic is sound for validating that duplicate purchases for the same productId are blocked.


849-880: Test validates one-time purchase blocking within the same product line.

The mock at line 853 correctly sets productLineId: 'g1' for an existing purchase, and the test product at line 867 also uses productLineId: 'g1'. This correctly tests the scenario where a purchase should be blocked when another exists in the same product line.


879-879: Error message correctly matches implementation.

The test assertion expects 'Customer already has a one-time purchase in this product line', which matches the implementation in payments.tsx at line 665. The fix is correct.


1167-1167: Test description and error message match implementation.

The test name at line 1167 correctly describes the scenario as "same line", and the error message at line 1206 expects 'Multiple include-by-default products configured in the same product line', which matches the error thrown in the implementation at line 356 of payments.tsx.

AGENTS.md (1)

89-89: Good addition for URL safety best practice.

The urlString tagged template literal utility is a sensible addition to the coding guidelines, as it automatically encodes interpolated values using encodeURIComponent(), preventing URL encoding issues and potential injection vulnerabilities. Recommending its use (or plain encodeURIComponent() as a fallback) for URL construction promotes both safety and consistency across the codebase.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (8)

61-67: LGTM - Helper function for ID generation

The toIdFormat function correctly handles basic slug generation. It converts to lowercase, replaces non-alphanumeric sequences with hyphens, and trims leading/trailing hyphens.


572-574: LGTM - Consistent rename in prompt generation

The prompt generation correctly uses productLineId instead of the old catalogId.


851-851: LGTM - Filter updated to use productLineId

The add-on filter logic correctly uses productLineId for determining which products could be add-ons.


1496-1521: LGTM - Type definitions updated consistently

The type renames and new callback props for CRUD operations on product lines are well-structured.


1527-1536: LGTM - State variables for product line management

The new state variables for managing product line editing and deletion are well-organized.


2168-2226: LGTM - Product line edit and delete dialogs

The dialogs for editing and deleting product lines are well-implemented with proper state management and clear user messaging about the consequences of deletion.


2455-2479: LGTM - Product line deletion with product migration

The deletion logic correctly moves products to "No product line" before removing the product line. The as any cast is justified by the comment explaining the dynamic nature of the config update.


2231-2237: LGTM - PageClient component setup

The component correctly uses the renamed types and props for the product line view.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (9)

56-56: LGTM!

Import correctly updated to use the renamed CreateProductLineDialog component.


105-106: LGTM!

The product line derivation correctly handles the lookup with proper null checks and fallback behavior.


135-141: LGTM!

Props and function signature correctly updated to use productLineName with proper nullable typing.


216-219: LGTM!

Product line display correctly uses human-readable "Product Line:" label and properly handles the null case.


259-282: LGTM!

The productLineOptions derivation correctly:

  • Determines customer type compatibility from existing products in each product line
  • Disables incompatible options with clear explanatory text
  • Uses __none__ sentinel value for "No product line" option
  • Has complete dependency array

304-316: LGTM!

The filter correctly uses productLineId for matching products in the same product line, with proper dependencies.


338-357: LGTM!

Both handlers correctly implement the product line operations:

  • handleProductLineUpdate properly converts the __none__ sentinel to null
  • handleCreateProductLine sequentially creates the product line and assigns the product

417-429: LGTM!

Grid item correctly configured with proper user-facing labels, tooltip, and the "Create new product line" action.


627-632: LGTM!

CreateProductLineDialog correctly integrated with proper state management and callbacks.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 3

🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx:
- Around line 387-389: The handleCancel function uses window.history.back()
which fails when the page is opened directly; update handleCancel to check
history length and fall back to a deterministic navigation to the products list
(e.g., if (window.history.length > 1) window.history.back(); else
router.push(`/projects/${projectId}/payments/products`)). Use the existing
projectId value and your router/navigation helper (e.g., next/navigation's
useRouter or a navigate function) to implement the fallback so cancel always
returns the user to the products list.

In `@packages/stack-shared/src/config/schema-fuzzer.test.ts`:
- Around line 97-99: productLineId (and legacy catalogId/groupId) values include
"some-other-product-line-id" but the productLines fuzzer config only defines
"some-product-line-id", causing potential invalid references during cross-field
validation; update the productLines fuzzer configuration to also include
"some-other-product-line-id" (or alternatively change the
productLineId/catalogId/groupId arrays to only reference existing IDs) so
generated products always reference defined product lines; locate symbols
productLineId, catalogId, groupId and the productLines fuzzer entries in
schema-fuzzer.test.ts to apply the fix.

In `@packages/stack-shared/src/config/schema.ts`:
- Around line 388-411: The current inference loop treats res as flat
dot-notation keys and thus never finds keys like
"payments.products.{id}.customerType"; replace this logic with the same nested
traversal utility used elsewhere (e.g., use mapProperty/renameProperty) to
iterate payments.products and collect productLineId -> customerType, or
explicitly traverse res.payments.products and res.payments.productLines objects
to build productLineCustomerTypes and then set missing
payments.productLines.{id}.customerType; target symbols: res,
productLineCustomerTypes, mapProperty/renameProperty, payments.products,
payments.productLines.
♻️ Duplicate comments (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

2162-2162: Inconsistent button text casing.

The button text "Create ProductLine" should be "Create Product Line" (with a space) for consistency with the rest of the UI copy.

Proposed fix
-              Create ProductLine
+              Create Product Line
packages/stack-shared/src/config/schema.ts (1)

152-158: Fix backwards compatibility for empty productLines in migration.

The customerType field is required in the schema but will remain undefined after migration for product lines without associated products. The migration at lines 388-410 only infers customerType from products that reference the product line, leaving empty productLines unable to satisfy schema validation.

Consider either:

  1. Detecting old configs with empty catalogs and assigning them a default customerType, or
  2. Making customerType optional in the schema to gracefully handle this edge case.
🧹 Nitpick comments (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

2130-2132: Consider user-friendly error message.

The getUserSpecifiedIdErrorMessage("productLineId") may produce a technical error message. Consider whether this message will be clear to dashboard users.

#!/bin/bash
# Check what error message is generated for productLineId
rg -n "getUserSpecifiedIdErrorMessage" --type ts -A 3 | head -40
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

336-365: Replace try-catch-all with runAsynchronouslyWithAlert for proper error handling.

Per coding guidelines, this code has two issues:

  1. Uses try-catch-all pattern which should be avoided
  2. Uses toast for errors (line 361) when alerts should be used for blocking errors
♻️ Suggested refactor using runAsynchronouslyWithAlert
 const handleSave = async () => {
   const validationErrors = validateForm();
   if (Object.keys(validationErrors).length > 0) {
     setErrors(validationErrors);
     return;
   }

   setIsSaving(true);
-  try {
+  runAsynchronouslyWithAlert(async () => {
     const product: Product = {
       displayName,
       customerType,
       productLineId: productLineId || undefined,
       isAddOnTo: isAddOn ? Object.fromEntries(isAddOnTo.map(id => [id, true])) : false,
       stackable,
       prices: freeByDefault ? 'include-by-default' : prices,
       includedItems,
       serverOnly,
       freeTrial,
     };

     await project.updateConfig({ [`payments.products.${productId}`]: product });
     toast({ title: "Product created" });
     router.push(`/projects/${projectId}/payments/products`);
-  } catch (error) {
-    toast({ title: "Failed to create product", variant: "destructive" });
-  } finally {
+  }).finally(() => {
     setIsSaving(false);
-  }
+  });
 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 653fa44 and 882bb95.

📒 Files selected for processing (9)
  • AGENTS.md
  • README.md
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • packages/stack-shared/src/config/schema.ts
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/switch/route.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry
Use .toMatchInlineSnapshot over other selectors in tests when possible, and check/modify snapshot-serializer.ts to understand how snapshots are formatted

Files:

  • packages/stack-shared/src/config/schema-fuzzer.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • packages/stack-shared/src/config/schema.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • packages/stack-shared/src/config/schema.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • packages/stack-shared/src/config/schema.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
packages/stack-shared/src/config/schema.ts

📄 CodeRabbit inference engine (AGENTS.md)

Whenever making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts

Files:

  • packages/stack-shared/src/config/schema.ts
🧠 Learnings (9)
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • packages/stack-shared/src/config/schema-fuzzer.test.ts
  • packages/stack-shared/src/config/schema.ts
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Code defensively using `?? throwErr(...)` instead of non-null assertions, with good error messages explicitly stating violated assumptions

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use ES6 maps instead of records wherever possible

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Try to avoid the `any` type. When using `any`, leave a comment explaining why and how the type system fails or how errors would still be caught

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use `Date.now()` for measuring elapsed (real) time; instead use `performance.now()`

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use `usePathname` instead of `await params`)

Applied to files:

  • AGENTS.md
📚 Learning: 2025-12-03T07:19:44.433Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 1040
File: packages/stack-shared/src/interface/crud/oauth-providers.ts:62-87
Timestamp: 2025-12-03T07:19:44.433Z
Learning: In packages/stack-shared/src/interface/crud/oauth-providers.ts and similar CRUD files, the tag "Oauth" (not "OAuth") is the correct capitalization format as it's used by the documentation generation system and follows OpenAPI conventions.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧬 Code graph analysis (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts (1)
  • Product (9-9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useProjectId (46-53)
packages/stack-shared/src/utils/urls.tsx (1)
  • urlString (314-316)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)
  • PageClient (178-1031)
packages/stack-shared/src/config/schema.ts (2)
packages/stack-shared/src/schema-fields.ts (2)
  • yupRecord (283-322)
  • yupObject (247-251)
packages/stack-shared/src/utils/objects.tsx (1)
  • getOrUndefined (545-548)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
🔇 Additional comments (21)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (9)

51-67: LGTM!

The urlString import aligns with the coding guideline to prefer urlString/encodeURIComponent for URLs. The toIdFormat helper correctly converts display names to valid ID format.


1496-1521: LGTM!

The ProductLineViewProps type correctly includes customerType in onCreateProductLine, and the ProductLineTypeKey type provides proper grouping semantics.


2019-2021: LGTM!

The productLineId query parameter is correctly passed using urlString for proper URL encoding. Per the relevant code snippets, the receiving page (new/page-client.tsx) reads this parameter via searchParams.get("productLineId").


2438-2448: LGTM!

The onSaveProductWithGroup handler correctly updates only the product without overwriting the product line. The onCreateProductLine handler properly includes customerType in the saved data.


2454-2478: LGTM!

The onDeleteProductLine handler correctly moves products to "No product line" before deletion. The as any cast includes appropriate justification per coding guidelines.


1716-1755: LGTM!

The product line header with hover-to-reveal edit/delete buttons follows good UX patterns. The transition classes correctly use hover:transition-none per coding guidelines for snappy hover interactions.


2168-2227: LGTM!

The edit and delete dialogs are well-implemented with proper state management and appropriate use of toast for success feedback.


2386-2423: LGTM!

The handleDeleteProduct function correctly handles cascading deletion of empty product lines and uses proper immutable update patterns with typedFromEntries/typedEntries.


1653-1666: LGTM!

The emptyProductLines computation correctly identifies product lines without any products for rendering empty state UI.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (7)

33-36: LGTM! Import changes align with product line terminology.

The useSearchParams usage is appropriate here since this is a client component, which is the preferred approach per coding guidelines for keeping pages static.


240-246: LGTM! Property mapping correctly updated.


289-299: LGTM! Handler correctly uses runAsynchronouslyWithAlert.

The config path update to payments.productLines.${productLine.id} aligns with the backend schema changes.


320-327: LGTM! Validation logic correctly enforces product line consistency.

The check ensures all selected add-on products belong to the same product line, which maintains the mutual exclusivity constraint.


428-439: LGTM! Code generation correctly reflects product line terminology.


902-932: LGTM! Product line selector UI correctly implemented.

The select component properly handles the three cases: no product line, existing product lines, and creating a new one. Terminology is consistent throughout.


996-1000: LGTM! Dialog correctly integrated with product line creation flow.

AGENTS.md (1)

89-91: LGTM!

These new guidelines are clear, actionable, and align well with defensive coding practices. The path notation guidance (line 90) is particularly relevant for the config migration changes in this PR.

packages/stack-shared/src/config/schema.ts (3)

15-15: LGTM!

The getOrUndefined import is correctly added to support the new cross-field validation logic.


170-193: LGTM!

The cross-field validation is well-implemented:

  • Safely handles missing productLineId by continuing
  • Provides clear error messages with specific IDs for debugging
  • Uses getOrUndefined defensively to check productLine existence before accessing properties

613-619: LGTM!

The defaults are correctly updated to reflect the rename. The customerType: undefined default is appropriate since this field must be explicitly set when creating new product lines.

packages/stack-shared/src/config/schema-fuzzer.test.ts (1)

68-83: LGTM!

Excellent test coverage for the migration paths. The fuzzer config correctly includes:

  • New productLines structure with customerType (lines 68-73)
  • Legacy catalogs and groups structures (lines 74-83) to ensure migrations work

This ensures the fuzzer exercises both fresh configs and migrated legacy configs.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

336-365: Replace try-catch with runAsynchronouslyWithAlert for error handling.

The current implementation uses try-catch with toast for error display, which violates the coding guidelines: "NEVER try-catch-all" and "For blocking alerts and errors, never use toast, as they are easily missed by the user."

🛠️ Suggested refactor
-  const handleSave = async () => {
+  const handleSave = () => {
     const validationErrors = validateForm();
     if (Object.keys(validationErrors).length > 0) {
       setErrors(validationErrors);
       return;
     }
 
     setIsSaving(true);
-    try {
+    runAsynchronouslyWithAlert(async () => {
       const product: Product = {
         displayName,
         customerType,
         productLineId: productLineId || undefined,
         isAddOnTo: isAddOn ? Object.fromEntries(isAddOnTo.map(id => [id, true])) : false,
         stackable,
         prices: freeByDefault ? 'include-by-default' : prices,
         includedItems,
         serverOnly,
         freeTrial,
       };
 
       await project.updateConfig({ [`payments.products.${productId}`]: product });
       toast({ title: "Product created" });
       router.push(`/projects/${projectId}/payments/products`);
-    } catch (error) {
-      toast({ title: "Failed to create product", variant: "destructive" });
-    } finally {
+    }, {
+      finally: () => setIsSaving(false),
+    });
-      setIsSaving(false);
-    }
   };

Based on coding guidelines, use runAsynchronouslyWithAlert instead of general try-catch error handling.

♻️ Duplicate comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

269-272: Customer type selection should reset productLineId initialized from URL.

This remains an issue from a previous review: when a user arrives via URL with ?productLineId=... and then selects a different customer type, the productLineId state is not reset, unlike when changing via the dropdown (handleCustomerTypeChange). This can cause schema validation failures at save time.

🛠️ Suggested fix
 const handleSelectCustomerType = (type: 'user' | 'team' | 'custom') => {
   setCustomerType(type);
   setHasSelectedCustomerType(true);
+  // Reset productLineId if it was pre-set from URL for a different customer type
+  if (productLineId) {
+    const existingProductLine = paymentsConfig.productLines[productLineId];
+    if (existingProductLine && existingProductLine.customerType !== type) {
+      setProductLineId("");
+    }
+  }
 };
🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

906-936: Consider filtering product lines by customer type in the dropdown.

The dropdown displays all product lines from paymentsConfig.productLines without filtering by customerType. Users might inadvertently select a product line created for a different customer type (e.g., selecting a "team" product line when creating a "user" product).

💡 Optional enhancement
-                      {Object.entries(paymentsConfig.productLines).map(([id, productLine]) => (
+                      {Object.entries(paymentsConfig.productLines)
+                        .filter(([, productLine]) => productLine.customerType === customerType)
+                        .map(([id, productLine]) => (
                         <SelectItem key={id} value={id} className="rounded-lg">
                           {productLine.displayName || id}
                         </SelectItem>
                       ))}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d68cfd3 and 3f0711b.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/components/data-table/payment-product-table.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/components/data-table/payment-product-table.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/dashboard/src/components/data-table/payment-product-table.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/dashboard/src/components/data-table/payment-product-table.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (2)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronouslyWithAlert (312-328)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (8)
apps/dashboard/src/components/data-table/payment-product-table.tsx (3)

11-15: Clean type alias extraction.

The BranchPayments type alias centralizes the schema inference and improves readability. Using NonNullable is appropriate here to ensure the type is non-nullable when accessing nested properties like products.


54-54: Prop typing aligned with the new type alias.

The updated prop type is consistent with the BranchPayments type alias, maintaining type safety while improving code readability.


106-116: Consider the toast usage for delete confirmation.

The toast is used here for success feedback after deletion, which is generally acceptable. However, since deletion is a destructive action, verify this aligns with your UX patterns — the guideline mentions avoiding toast for blocking alerts and errors, but a success notification after an irreversible action might benefit from being more prominent if you want users to clearly confirm the action completed.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (5)

289-298: LGTM!

The handler correctly uses runAsynchronouslyWithAlert for async operations with proper error handling, and the rename from catalog to product line is consistent.


387-393: LGTM!

The cancel handler now properly checks window.history.length before calling back(), with a fallback to navigate to the products list. This addresses the concern from the previous review about direct URL navigation.


320-327: LGTM!

The validation logic correctly renames catalog references to product line while maintaining the same behavior for checking add-on product consistency.


1000-1004: LGTM!

The dialog component is correctly renamed and wired with appropriate props matching the CreateProductLineDialog interface.


436-436: LGTM!

The inline product code generation and prompt generation correctly use productLineId terminology, ensuring copied code snippets are consistent with the new naming.

Also applies to: 473-473

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

351-371: Refactor to use runAsynchronouslyWithAlert instead of try-catch-all with toast.

Per coding guidelines, avoid try-catch-all patterns and never use toast for error notifications (they're easily missed). Consider refactoring to use runAsynchronouslyWithAlert:

Suggested refactor
 const handleSave = async () => {
   const validationErrors = validateForm();
   if (Object.keys(validationErrors).length > 0) {
     setErrors(validationErrors);
     return;
   }

   setIsSaving(true);
-  try {
-    const product: Product = {
-      displayName,
-      customerType,
-      productLineId: productLineId || undefined,
-      isAddOnTo: isAddOn ? Object.fromEntries(isAddOnTo.map(id => [id, true])) : false,
-      stackable,
-      prices: freeByDefault ? 'include-by-default' : prices,
-      includedItems,
-      serverOnly,
-      freeTrial,
-    };
-
-    await project.updateConfig({ [`payments.products.${productId}`]: product });
-    toast({ title: "Product created" });
-    router.push(`/projects/${projectId}/payments/products`);
-  } catch (error) {
-    toast({ title: "Failed to create product", variant: "destructive" });
-  } finally {
-    setIsSaving(false);
-  }
+  runAsynchronouslyWithAlert(async () => {
+    const product: Product = {
+      displayName,
+      customerType,
+      productLineId: productLineId || undefined,
+      isAddOnTo: isAddOn ? Object.fromEntries(isAddOnTo.map(id => [id, true])) : false,
+      stackable,
+      prices: freeByDefault ? 'include-by-default' : prices,
+      includedItems,
+      serverOnly,
+      freeTrial,
+    };
+
+    await project.updateConfig({ [`payments.products.${productId}`]: product });
+    toast({ title: "Product created" });
+    router.push(`/projects/${projectId}/payments/products`);
+  }).finally(() => setIsSaving(false));
 };

Based on coding guidelines: "NEVER try-catch-all" and "For blocking alerts and errors, never use toast".

🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx:
- Around line 933-937: The product-line dropdown currently maps all entries from
paymentsConfig.productLines, causing mismatches with the selected customer type;
update the rendering to first filter Object.entries(paymentsConfig.productLines)
to only include lines whose productLine.customerType matches the form's
customerType (or includes it if productLine.customerType is an array) before
mapping to SelectItem; locate the dropdown mapping near the SelectItem render
and use the same customerType state/prop used by handleCreateProductLine to
ensure only customer-type-compatible product lines are shown.
♻️ Duplicate comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

195-202: Product line URL parameter is validated for existence but not customer type compatibility.

The validation correctly checks if the productLineId exists in config, which addresses the previous crash concern. However, when handleSelectCustomerType is called (line 276), the pre-selected productLineId from URL isn't validated against the newly selected customer type. A user could navigate with ?productLineId=team-line, select "user" customer type, and end up with a mismatched product line.

Consider resetting productLineId in handleSelectCustomerType if it doesn't match the selected customer type:

Suggested fix
 const handleSelectCustomerType = (type: 'user' | 'team' | 'custom') => {
   setCustomerType(type);
   setHasSelectedCustomerType(true);
+  // Reset productLineId if it doesn't match the selected customer type
+  if (productLineId) {
+    const productLine = paymentsConfig.productLines[productLineId];
+    if (productLine?.customerType !== type) {
+      setProductLineId("");
+    }
+  }
 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3366ecb and 7c46e05.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build (22.x)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: docker
  • GitHub Check: setup-tests
🔇 Additional comments (5)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (5)

296-306: LGTM!

The handleCreateProductLine function correctly uses runAsynchronouslyWithAlert for async error handling and properly updates the config with the new product line.


394-400: LGTM!

The cancel handler now properly checks history length before navigating back, with a fallback to the products list page. This addresses the previous concern about direct URL access.


328-333: LGTM!

The add-on validation correctly ensures all selected products belong to the same product line, with updated terminology.


439-450: LGTM!

The inline code generation and prompt functions correctly include productLineId when set, with proper conditional formatting.

Also applies to: 476-481


1007-1011: LGTM!

The CreateProductLineDialog is correctly wired up with the renamed state and handler.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

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: 3

🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx:
- Around line 298-310: The filter in availableProducts fails when productLineId
uses inconsistent null/undefined so normalize both sides before comparing:
inside the useMemo filter for config.payments.products (the availableProducts
computation) coerce p.productLineId and product.productLineId to a consistent
value (e.g., use the nullish coalescing result like (p.productLineId ?? null)
and (product.productLineId ?? null) or stringify both) and compare those
normalized values along with id !== productId and p.customerType ===
product.customerType so add-on candidates aren't incorrectly excluded.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:
- Around line 2131-2136: Replace the non-blocking toast calls used for
validation with a blocking alert mechanism: where the code currently calls
toast({ title: getUserSpecifiedIdErrorMessage("productLineId"), variant:
"destructive" }) and toast({ title: "Product line ID already exists", variant:
"destructive" }) (in the product line creation/validation branch that checks
groups and productLineId), render or invoke a blocking Alert (or window.alert
for a quick fix) that presents the same message and prevents continuation; if
your app uses a UI Alert component, set it to an error/destructive state and
surface it synchronously instead of calling toast so the user must acknowledge
the validation failure before proceeding.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx:
- Around line 574-577: The span rendering uses
existingProductLines[product.productLineId].displayName without guarding for a
missing entry, which throws if that key is undefined; update the expression in
product-dialog.tsx (the block that references product.productLineId and
existingProductLines) to safely access the metadata (use optional chaining) and
fall back to product.productLineId or a safe placeholder when
existingProductLines[product.productLineId] is undefined so the UI doesn't crash
for deleted/unknown product lines.
♻️ Duplicate comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

276-279: Reset mismatched productLineId on initial customer type selection.

If the URL preselects a product line with a different customer type, the first-step selection keeps the invalid productLineId, and the mismatch isn’t corrected until save. Consider clearing it up-front.

🛠️ Proposed fix
 const handleSelectCustomerType = (type: 'user' | 'team' | 'custom') => {
   setCustomerType(type);
   setHasSelectedCustomerType(true);
+  if (productLineId) {
+    const line = paymentsConfig.productLines[productLineId];
+    if (!line || line.customerType !== type) {
+      setProductLineId("");
+    }
+  }
 };
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

1644-1646: Guard against deleted product lines when sorting/rendering.

groups[productLineId] can be undefined when drafts reference deleted product lines, which will crash during sorting/rendering.

🛠️ Proposed fix
-const nameA = a.productLineId ? (groups[a.productLineId].displayName || a.productLineId) : '';
-const nameB = b.productLineId ? (groups[b.productLineId].displayName || b.productLineId) : '';
+const nameA = a.productLineId ? (groups[a.productLineId]?.displayName || a.productLineId) : '';
+const nameB = b.productLineId ? (groups[b.productLineId]?.displayName || b.productLineId) : '';

-const groupName = productLineId ? (groups[productLineId].displayName || productLineId) : 'No product line';
+const groupName = productLineId ? (groups[productLineId]?.displayName || productLineId) : 'No product line';

Also applies to: 1700-1701

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (1)

559-586: User-facing copy: “product line” should be two words.

✏️ Suggested copy tweak
-                    <p className="text-sm text-muted-foreground">No other products with the same customer type and productLine</p>
+                    <p className="text-sm text-muted-foreground">No other products with the same customer type and product line</p>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c46e05 and 90cebb7.

📒 Files selected for processing (4)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧠 Learnings (2)
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to {**/apps-frontend.tsx,**/apps-config.ts} : To update the list of available apps, edit `apps-frontend.tsx` and `apps-config.ts`. Check existing apps for inspiration when implementing new apps or pages

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
📚 Learning: 2025-12-03T07:19:44.433Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 1040
File: packages/stack-shared/src/interface/crud/oauth-providers.ts:62-87
Timestamp: 2025-12-03T07:19:44.433Z
Learning: In packages/stack-shared/src/interface/crud/oauth-providers.ts and similar CRUD files, the tag "Oauth" (not "OAuth") is the correct capitalization format as it's used by the documentation generation system and follows OpenAPI conventions.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧬 Code graph analysis (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
🔇 Additional comments (17)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (7)

181-202: URL productLineId preselection is safely validated.

Good guard against invalid query params before initializing state.


285-305: Customer-type change now clears incompatible product-line state.

Nice reset of product line and add-on selections when customer type changes.


327-333: Add‑on validation is correctly constrained to a single product line.


392-397: Cancel/back fallback looks solid.


413-478: Inline code + prompt now include productLineId when set.


911-937: Product line dropdown is correctly filtered by customer type.


1007-1011: CreateProductLineDialog wiring looks correct.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (4)

134-140: Add‑on validation now enforces single product line.


444-451: Customer type change correctly clears productLineId.


470-504: Product line selector and filtering are aligned with customer type.


776-783: CreateProductLineDialog integration looks good.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (3)

61-67: ID normalization helper is clean and reusable.


1879-1889: “Add product” now preserves productLineId in the URL.


2444-2447: Product line creation now persists customerType.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx (3)

105-106: Product line header wiring looks consistent.
Clean fallback to displayName/id and conditional rendering.

Also applies to: 123-123, 138-141, 216-220


202-204: Navigation label/route for Product Lines looks correct.


56-56: Product line selection + create dialog integration looks solid.
Filtering by customer type and the create flow wiring are coherent.

Also applies to: 257-276, 332-354, 417-425, 624-629

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

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: 2

🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx:
- Around line 2359-2383: The onDeleteProductLine handler currently moves
persisted products to no product line but leaves in-memory drafts orphaned;
before calling project.updateConfig you should migrate or clear drafts that
reference the deleted productLineId by updating their product.productLineId to
undefined (or relocating them) and then call the drafts setter/callback (e.g.,
setDrafts or migrateDrafts) with the updated drafts; to do this, accept a drafts
updater into the handler (or lift the handler so it has access to setDrafts),
iterate the existing drafts to modify any draft whose product.productLineId ===
productLineId, apply the updated drafts state, and only then proceed to build
updateConfig and call project.updateConfig.

In `@packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts`:
- Around line 81-105: The migration currently uses result.set(catalogId,
customerType) (and similarly result.set(value, obj[customerTypeKey])) which
silently overwrites existing entries; update both places to check if
result.has(catalogId) (or result.has(value)) and if so verify the existing value
equals the new customerType, otherwise throw an Error that includes the
conflicting catalogId and both customerType values to force manual resolution;
apply this logic in the nested-branch (where catalogId is derived via
findPropertyValue) and in the root flat-format branch (where customerTypeKey is
used), leaving typedEntries, isObjectLike, and findPropertyValue unchanged.
♻️ Duplicate comments (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

2050-2055: Use alerts instead of toasts for blocking validation errors.

These validation errors prevent the action from completing. Per coding guidelines, blocking errors should use alerts rather than toasts since toasts can be easily missed.

🔧 Suggested fix

Replace the toast calls with alert dialogs or the standard alert() function:

 if (!isValidUserSpecifiedId(id)) {
-  toast({ title: getUserSpecifiedIdErrorMessage("productLineId"), variant: "destructive" });
+  alert(getUserSpecifiedIdErrorMessage("productLineId"));
   return;
 }
 if (Object.prototype.hasOwnProperty.call(groups, id)) {
-  toast({ title: "Product line ID already exists", variant: "destructive" });
+  alert("Product line ID already exists");
   return;
 }

Based on coding guidelines requiring alerts for blocking errors.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (1)

289-295: Reset productLineId when customer type is chosen via the selection screen.

The dropdown path clears productLineId, but the initial selection flow doesn’t. If a user goes “Back” and picks a different customer type, the previous productLineId can linger and cause a mismatch on save.

🛠️ Suggested fix
 const handleSelectCustomerType = (type: 'user' | 'team' | 'custom') => {
   setCustomerType(type);
+  setProductLineId("");
+  setIsAddOnTo([]);
   setHasSelectedCustomerType(true);
 };
🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

1610-1631: Consider using const for the non-reassigned variable.

customerType at line 1612 is never reassigned, so const would be more appropriate.

✨ Suggested fix
 const productLinesToRender = useMemo(() => {
   const productLines = Object.entries(groups).map(([id, productLine]) => {
-    let customerType = productLine.customerType;
+    const customerType = productLine.customerType;
     return {
       id,
       displayName: productLine.displayName || id,
       customerType,
     };
   });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90cebb7 and eba7ad3.

📒 Files selected for processing (4)
  • apps/backend/prisma/seed.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/prisma/seed.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧠 Learnings (6)
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to {**/apps-frontend.tsx,**/apps-config.ts} : To update the list of available apps, edit `apps-frontend.tsx` and `apps-config.ts`. Check existing apps for inspiration when implementing new apps or pages

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts
📚 Learning: 2025-12-03T07:19:44.433Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 1040
File: packages/stack-shared/src/interface/crud/oauth-providers.ts:62-87
Timestamp: 2025-12-03T07:19:44.433Z
Learning: In packages/stack-shared/src/interface/crud/oauth-providers.ts and similar CRUD files, the tag "Oauth" (not "OAuth") is the correct capitalization format as it's used by the documentation generation system and follows OpenAPI conventions.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : For blocking alerts and errors, never use `toast`, as they are easily missed by the user. Instead, use alerts

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧬 Code graph analysis (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (2)
packages/stack-shared/src/config/schema.ts (1)
  • CompleteConfig (1118-1118)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts (2)
packages/stack-shared/src/utils/objects.tsx (2)
  • typedEntries (263-265)
  • isObjectLike (579-581)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: setup-tests
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: lint_and_build (latest)
🔇 Additional comments (22)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (4)

61-67: LGTM!

The toIdFormat helper is well-implemented with proper lowercase conversion, non-alphanumeric replacement, and hyphen trimming at boundaries.


851-852: LGTM!

The add-on filtering logic correctly matches products within the same product line.


1496-1521: LGTM!

The updated types properly define the product line CRUD callbacks with appropriate parameters including customerType.


2343-2358: LGTM!

The onSaveProductWithGroup handler now correctly saves only the product without overwriting the product line's properties. The onCreateProductLine callback properly includes customerType.

packages/stack-shared/src/config/migrate-catalogs-to-product-lines.ts (7)

8-26: LGTM for the path-aware rename helper.

Recursion + segment mapping looks solid, and the dot-guard on newName is a good safety check.


116-128: LGTM for dot-notation fallback lookup.


134-151: LGTM for catalog ID enumeration across formats.


158-207: Removal logic covers nested + flat shapes nicely.


212-283: CustomerType injection handles mixed formats cleanly.


286-475: Nice breadth of migration tests across formats and edge cases.


41-69: Migration is properly wired. The function is imported at line 19 and invoked in the migration pipeline at line 379 within the correct version gate (isBranchOrHigher), ensuring existing configs are upgraded automatically.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx (11)

33-36: LGTM for adding search params + product line dialog imports.


187-199: Good validation of URL prefill inputs.


205-218: State init aligns with prefill + new product line dialog.


251-272: Product line ID now flows into preview and existing product list cleanly.


300-309: LGTM for creating product lines with the active customerType.


332-337: Addon validation updated for product lines looks good.


354-366: LGTM for persisting productLineId on product creation.


396-401: Back/cancel fallback looks solid.


441-482: Inline code + prompt reflect product line selection correctly.


915-946: Filtering product lines by customer type is a good guardrail.


1011-1014: Dialog wiring looks correct.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@N2D4 N2D4 enabled auto-merge (squash) January 16, 2026 18:57
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

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

1855-1865: Keep productLineId in the Add‑product link even when customerType is missing.
If a migrated config lacks customerType, the current conditional drops productLineId, so the new product can’t be pre‑scoped to its line. Consider always passing productLineId and only appending customerType when present.

🔧 Suggested adjustment
- <Link href={productLineId && customerType ? urlString`/projects/${projectId}/payments/products/new?productLineId=${productLineId}&customerType=${customerType}` : urlString`/projects/${projectId}/payments/products/new`}>
+ <Link
+   href={
+     productLineId
+       ? (customerType
+           ? urlString`/projects/${projectId}/payments/products/new?productLineId=${productLineId}&customerType=${customerType}`
+           : urlString`/projects/${projectId}/payments/products/new?productLineId=${productLineId}`)
+       : urlString`/projects/${projectId}/payments/products/new`
+   }
+ >
♻️ Duplicate comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (1)

777-785: Product line creation doesn't persist to config.

The onCreate handler only updates local state but doesn't save the new product line to the project config. When the user subsequently saves the product with this newly created product line ID, schema validation will fail because the product line doesn't exist in payments.productLines.

The comment on line 781 acknowledges this gap. Consider adding an onCreateProductLine callback prop (as suggested in past reviews) to persist the product line before setting it locally.

🧹 Nitpick comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (1)

677-677: Consider aligning hover transition pattern with the rest of the file.

The coding guidelines recommend using hover:transition-none to avoid hover-enter transitions. This line has transition-colors but lacks the hover:transition-none modifier that other similar elements in this file use.

🔧 Suggested fix
-                            className="px-3 py-3 hover:bg-muted/50 flex items-center justify-between transition-colors"
+                            className="px-3 py-3 hover:bg-muted/50 flex items-center justify-between transition-colors hover:transition-none"
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (2)

2041-2065: Wrap product‑line async actions with runAsynchronouslyWithAlert.
New create/edit/delete handlers are async; using the standard helper keeps error handling consistent and surfaces failures to users. Apply the same pattern to the edit and delete actions as well.

♻️ Example update (apply similarly to edit/delete)
- onClick={async () => {
+ onClick={() => runAsynchronouslyWithAlert(async () => {
    const id = newProductLineId.trim();
    const displayName = newProductLineDisplayName.trim();
    if (!id || !displayName) return;
    if (!isValidUserSpecifiedId(id)) {
      alert(getUserSpecifiedIdErrorMessage("productLineId"));
      return;
    }
    if (Object.prototype.hasOwnProperty.call(groups, id)) {
      alert("Product line ID already exists");
      return;
    }

    await onCreateProductLine(id, displayName, newProductLineCustomerType);

    setNewProductLineDisplayName("");
    setNewProductLineId("");
    setHasManuallyEditedProductLineId(false);
    toast({ title: "Product line created" });
- }}
+ })}

Based on learnings, ...

Also applies to: 2097-2103, 2119-2126


2166-2171: Avoid non‑null assertion when pushing to the group map.
Prefer an explicit guard or ?? throwErr(...) to make the assumption explicit and safer.

🔧 Safer variant
-      groups.get(productLineId)!.push({ id, product });
+      const group = groups.get(productLineId) ?? throwErr(`Missing group for productLineId: ${String(productLineId)}`);
+      group.push({ id, product });

As per coding guidelines, ...

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dba9d85 and c7bbbf6.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧠 Learnings (5)
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to **/*.{ts,tsx} : Code defensively using `?? throwErr(...)` instead of non-null assertions, with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx
📚 Learning: 2025-12-03T07:19:44.433Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 1040
File: packages/stack-shared/src/interface/crud/oauth-providers.ts:62-87
Timestamp: 2025-12-03T07:19:44.433Z
Learning: In packages/stack-shared/src/interface/crud/oauth-providers.ts and similar CRUD files, the tag "Oauth" (not "OAuth") is the correct capitalization format as it's used by the documentation generation system and follows OpenAPI conventions.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : For blocking alerts and errors, never use `toast`, as they are easily missed by the user. Instead, use alerts

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2026-01-13T18:14:29.993Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.993Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (2)
packages/stack-shared/src/utils/objects.tsx (1)
  • getOrUndefined (545-548)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/create-product-line-dialog.tsx (1)
  • CreateProductLineDialog (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: build (22.x)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (6)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-dialog.tsx (5)

8-10: LGTM!

Import changes correctly support the catalog → product line rename. The getOrUndefined utility enables defensive property access.


136-141: LGTM!

Validation logic correctly enforces that add-ons target products within the same product line. The Set-based approach handles both specified and unspecified (undefined) product lines properly.


448-452: LGTM!

Correctly resets the product line selection when customer type changes, since product lines are customer-type-specific and the previous selection may no longer be valid.


471-506: LGTM!

The product line selector correctly filters by customer type and provides appropriate options for no selection and creating new product lines.


575-579: Previous concern addressed.

The use of getOrUndefined with optional chaining correctly guards against missing product line metadata, falling back to the raw productLineId when the product line doesn't exist. Based on learnings, this is good defensive coding.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-product-lines-view.tsx (1)

61-67: Clean auto‑ID normalization helper.
Nice, consistent slug formatting for display names.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@N2D4 N2D4 disabled auto-merge January 16, 2026 21:08
@N2D4 N2D4 merged commit 14c27bb into dev Jan 16, 2026
13 of 15 checks passed
@N2D4 N2D4 deleted the payments-improvements branch January 16, 2026 21:09
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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

// If not, treat it as "no product line" - this handles cases where URL params have mismatched types
const effectiveProductLineId = productLineId && paymentsConfig.productLines[productLineId].customerType === customerType
? productLineId
: "";
Copy link

Choose a reason for hiding this comment

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

Missing null safety when accessing product line customerType

Medium Severity

The expression paymentsConfig.productLines[productLineId].customerType accesses .customerType without first checking if the product line still exists. If the product line is deleted (e.g., by another browser tab or concurrent user) while this page is open, the component would crash with a TypeError when it re-renders with the updated config. Optional chaining (paymentsConfig.productLines[productLineId]?.customerType) would handle this edge case gracefully.

Fix in Cursor Fix in Web

},
add_ons: {
displayName: 'Add-ons',
customerType: 'team',
Copy link

Choose a reason for hiding this comment

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

Seed data has mismatched customerType between products and productLines

Medium Severity

The productLines in buildDummyPaymentsSetup() are defined with customerType: 'team', but all products referencing them (starter, growth, regression-addon) have customerType: 'user'. Similarly, the legacy subscription uses productLineId: 'workspace' with customerType: 'user', and the one-time purchase uses productLineId: 'add_ons' with customerType: 'custom'. The UI enforces that products can only belong to product lines with matching customerType, so this creates an invalid state that wouldn't occur through normal operations and could cause products to not appear under their product lines.

Additional Locations (2)

Fix in Cursor Fix in Web

@promptless
Copy link
Contributor

promptless bot commented Jan 21, 2026

📝 Documentation updates detected!

Updated existing suggestion: Document subscription plan switching feature

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.

3 participants