Skip to content

Conversation

@N2D4
Copy link
Contributor

@N2D4 N2D4 commented Jan 14, 2026

Note

Refactors the payments domain to use product lines instead of catalogs throughout backend and dashboard.

  • Rename fields: catalogId -> productLineId, catalogs -> productLines in seed, config, payments lib, and API routes (switching, product listing, purchase session/validation); update error messages and in-memory maps accordingly
  • Update dashboard UI: navigation, pages, dialogs, and copy (new product line views, creation/edit flows; remove old catalogs layout); adjust product creation/edit flows and deep links
  • Adjust tests: update unit/integration tests and add e2e tests ensuring legacy catalog(s) config still works via schema migration
  • Minor docs: add coding guidelines and README test flags note

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

Summary by CodeRabbit

  • Refactor
    • Renamed "Catalogs" to "Product Lines" across the payments UI, navigation, dialogs, labels, and validation text (including plan grouping, switching, and one-time purchase messages).
  • Tests
    • Added end-to-end tests to validate legacy (catalogs/catalogId) compatibility with the new product-lines/productLineId model.
  • Documentation
    • Minor README and guidelines updated with clarifying notes about commands and defensive practices.

✏️ 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 15, 2026 2:59am
stack-dashboard Error Error Jan 15, 2026 2:59am
stack-demo Ready Ready Preview, Comment Jan 15, 2026 2:59am
stack-docs Ready Ready Preview, Comment Jan 15, 2026 2:59am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

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

This PR renames payments "catalog" concepts to "product line" across backend, frontend, schema, seeds, and tests: catalogsproductLines, catalogIdproductLineId, plus variable/prop/message updates. No behavioral control-flow changes beyond nomenclature and related guards.

Changes

Cohort / File(s) Summary
Backend seeds
apps/backend/prisma/seed.ts
Seed data keys renamed: catalogsproductLines and catalogIdproductLineId across products, payments setup, dummy transactions/emails.
Backend API routes
apps/backend/src/app/api/.../payments/products/.../route.ts, .../switch/route.ts, .../purchases/purchase-session/route.tsx, .../purchases/validate-code/route.ts
Replaced catalogId-based grouping/guards/variables with productLineId equivalents; updated validation messages and map keys (e.g., switchOptionsByCatalogIdswitchOptionsByProductLineId).
Backend core logic & tests
apps/backend/src/lib/payments.tsx, apps/backend/src/lib/payments.test.tsx
Migrated functions and test fixtures to productLineId (ensureProductIdOrInlineProduct, validatePurchaseSession, getSubscriptions, conflict naming). Return shapes/props updated to use product-line nomenclature.
Dashboard UI — product lines pages
apps/dashboard/.../payments/product-lines/*, apps/dashboard/src/lib/apps-frontend.tsx
Renamed pages/components and navigation from Catalogs → Product Lines; updated metadata, titles, descriptions, and navigation hrefs.
Dashboard UI — product management
apps/dashboard/.../payments/products/* (many files: dialogs, product-dialog, page-client, list views, product-lines view, create-product-line-dialog, new page-client, pricing-section, etc.)
Replaced catalog APIs/components with productLine equivalents: props, state, handlers, labels, validations, grouping/sorting, and dialog flows. Introduced ID auto-generation helpers and create/update/delete product-line handlers.
Dashboard removed module
apps/dashboard/src/app/.../payments/catalogs/layout.tsx
Deleted old CatalogsLayout file (onboarding and Stripe setup UI removed from this path).
Config schema & migration
packages/stack-shared/src/config/schema.ts, schema-fuzzer.test.ts, schema-fields.ts
Schema keys renamed (payments.catalogspayments.productLines, catalogIdproductLineId); added customerType on product lines; added migration logic to translate legacy configs and infer customerType.
E2E tests
apps/e2e/tests/backend/endpoints/api/v1/payments/** (multiple new/updated tests)
Added/updated E2E tests to verify backward compatibility for legacy catalogs/catalogId configs and to assert updated product-line messages and validations.
Template, docs & misc
packages/template/.../payments-panel.tsx, AGENTS.md, README.md
Dialog copy updated ("product line"), minor README test-note change, and AGENTS.md defensive guidelines added.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code from catalog to line,
Renamed each field, aligned every sign.
IDs rebloomed, dialogs learned to say,
"Product Line" now leads the product way.
🥕 Small hop, big change — cheers to the devs who shine!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.68% 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 accurately summarizes the main change: renaming catalog to product line throughout the codebase.
Description check ✅ Passed The description clearly outlines the refactoring scope, lists key changes by area (backend, dashboard, tests, docs), and includes context about migration/compatibility.

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

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

📒 Files selected for processing (1)
  • packages/stack-shared/src/config/schema.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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.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:

  • packages/stack-shared/src/config/schema.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
{.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.ts
🧠 Learnings (1)
📚 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
🧬 Code graph analysis (1)
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)
⏰ 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: check_prisma_migrations (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: build (22.x)
🔇 Additional comments (4)
packages/stack-shared/src/config/schema.ts (4)

15-15: LGTM!

The getOrUndefined import is correctly added and used in the new cross-field validation at line 177.


152-158: Schema rename looks correct, but verify customerType handling in migration.

The rename from catalogs/catalogId to productLines/productLineId is properly implemented. The customerType field is correctly added to enforce type consistency.

Ensure the migration logic at lines 388-411 properly infers customerType for old configs that don't have it; otherwise, validation may fail for migrated configs.


170-193: LGTM!

The cross-field validation correctly ensures:

  1. Products reference existing product lines
  2. Product customerType matches its product line's customerType

Error messages are descriptive and include relevant IDs for debugging.


613-619: LGTM!

The default values correctly reflect the schema rename from catalogs/catalogId to productLines/productLineId. Setting customerType: undefined as default is appropriate since the migration should infer the value from existing products.

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


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.

Comment on lines 387 to 389
const handleCancel = () => {
router.push(`/projects/${projectId}/payments/products`);
window.history.back();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify behavior when page is opened directly without history.

window.history.back() will do nothing (or navigate away from the app) if the user opened this page directly via URL or in a new tab. Consider adding a fallback to navigate to the products list.

🛠️ Suggested fix with fallback
 const handleCancel = () => {
-  window.history.back();
+  if (window.history.length > 1) {
+    window.history.back();
+  } else {
+    router.push(`/projects/${projectId}/payments/products`);
+  }
 };
🤖 Prompt for AI Agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx
around lines 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.

Comment on lines +97 to +99
productLineId: ["some-product-line-id", "some-other-product-line-id"],
catalogId: ["some-product-line-id", "some-other-product-line-id"], // ensure migration works
groupId: ["some-product-line-id", "some-other-product-line-id"], // ensure migration works
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential fuzzer inconsistency: some-other-product-line-id is referenced but not defined.

The productLineId (and legacy catalogId/groupId) fields reference "some-other-product-line-id", but the productLines fuzzer config (lines 68-73) only defines "some-product-line-id". This could cause the cross-field validation to fail during fuzzing when a product references a non-existent product line.

Consider adding "some-other-product-line-id" to the productLines fuzzer config, or verify this is intentional to test error handling.

Suggested fix
     productLines: [{
       "some-product-line-id": [{
         displayName: ["Some Product Line", "Some Other Product Line"],
         customerType: ["user", "team", "custom"] as const,
       }],
+      "some-other-product-line-id": [{
+        displayName: ["Another Product Line"],
+        customerType: ["user", "team", "custom"] as const,
+      }],
     }],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
productLineId: ["some-product-line-id", "some-other-product-line-id"],
catalogId: ["some-product-line-id", "some-other-product-line-id"], // ensure migration works
groupId: ["some-product-line-id", "some-other-product-line-id"], // ensure migration works
productLines: [{
"some-product-line-id": [{
displayName: ["Some Product Line", "Some Other Product Line"],
customerType: ["user", "team", "custom"] as const,
}],
"some-other-product-line-id": [{
displayName: ["Another Product Line"],
customerType: ["user", "team", "custom"] as const,
}],
}],
🤖 Prompt for AI Agents
In `@packages/stack-shared/src/config/schema-fuzzer.test.ts` around lines 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.

Comment on lines +388 to +411
// BEGIN 2026-01-14: infer productLines.*.customerType from products if missing
if (isBranchOrHigher) {
// Build a map of productLineId -> customerType from products
const productLineCustomerTypes = new Map<string, string>();
for (const [key, value] of Object.entries(res)) {
const path = key.split(".");
if (path.length === 4 && path[0] === "payments" && path[1] === "products" && path[3] === "customerType") {
const productId = path[2];
const productLineIdKey = `payments.products.${productId}.productLineId`;
const productLineId = res[productLineIdKey];
if (productLineId && typeof value === "string") {
productLineCustomerTypes.set(productLineId, value);
}
}
}
// Apply inferred customerType to productLines that don't have one
for (const [productLineId, customerType] of productLineCustomerTypes) {
const customerTypeKey = `payments.productLines.${productLineId}.customerType`;
if (!(customerTypeKey in res)) {
res[customerTypeKey] = customerType;
}
}
}
// END
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find migrateConfigOverride function calls and how configs are structured
rg -n "migrateConfigOverride\|flattenConfigObject\|Object\.entries" packages/stack-shared/src/config/schema.ts -B3 -A3 | head -100

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look for the migrateConfigOverride function signature and how it processes inputs
rg -n "function migrateConfigOverride\|const migrateConfigOverride" packages/stack-shared/src/config/ -A20 | head -80

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for test files or examples showing config structure format
fd -e test.ts -e spec.ts packages/stack-shared/src/config/ | head -5

Repository: stack-auth/stack-auth

Length of output: 502


🏁 Script executed:

#!/bin/bash
# Check file size
wc -l packages/stack-shared/src/config/schema.ts

Repository: stack-auth/stack-auth

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Read the migrateConfigOverride function and surrounding context starting from line 300
sed -n '300,420p' packages/stack-shared/src/config/schema.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 5637


🏁 Script executed:

#!/bin/bash
# Search for where migrateConfigOverride is called/exported
rg -n "migrateConfigOverride|export.*migrate" packages/stack-shared/src/config/schema.ts | head -20

Repository: stack-auth/stack-auth

Length of output: 208


🏁 Script executed:

#!/bin/bash
# Read helper functions like mapProperty and renameProperty
sed -n '118,300p' packages/stack-shared/src/config/schema.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 7567


🏁 Script executed:

#!/bin/bash
# Search for function definitions related to property manipulation
rg -n "^function|^const.*=.*function|^const.*=.*=>" packages/stack-shared/src/config/schema.ts | grep -E "mapProperty|renameProperty|flattenConfigObject"

Repository: stack-auth/stack-auth

Length of output: 348


🏁 Script executed:

#!/bin/bash
# Read the mapProperty function implementation
sed -n '421,470p' packages/stack-shared/src/config/schema.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 3048


🏁 Script executed:

#!/bin/bash
# Read the renameProperty function implementation
sed -n '451,520p' packages/stack-shared/src/config/schema.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 4561


🏁 Script executed:

#!/bin/bash
# Check what typedEntries does
rg -n "function typedEntries|const typedEntries" packages/stack-shared/src/config/schema.ts -A10 | head -30

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look for where migrateConfigOverride is called to see the input format
rg -n "migrateConfigOverride\(" packages/stack-shared/src/ -B3 -A3 --type=ts | head -80

Repository: stack-auth/stack-auth

Length of output: 3804


🏁 Script executed:

#!/bin/bash
# Check the createFuzzerInput to see what format configs are in
rg -n "createFuzzerInput|function createFuzzerInput" packages/stack-shared/src/config/schema-fuzzer.test.ts -B5 -A15 | head -60

Repository: stack-auth/stack-auth

Length of output: 2770


🏁 Script executed:

#!/bin/bash
# Check the config override types to understand their structure
rg -n "NormalizedOverride|ConfigNormalizedOverride" packages/stack-shared/src/config/schema.ts | head -20

Repository: stack-auth/stack-auth

Length of output: 1689


🏁 Script executed:

#!/bin/bash
# Look for examples of how config objects are passed/used in migrations
rg -n "typedEntries" packages/stack-shared/src/ -A5 --type=ts | head -40

Repository: stack-auth/stack-auth

Length of output: 3522


🏁 Script executed:

#!/bin/bash
# Check if normalize/flatten happens to configs before migration
rg -n "normalize\|flatten" packages/stack-shared/src/config/format.ts -B2 -A5 | head -80

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look at the fuzzer test more carefully to understand config structure
sed -n '236,275p' packages/stack-shared/src/config/schema-fuzzer.test.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 2086


🏁 Script executed:

#!/bin/bash
# Inspect what format.ts defines for config structure
head -100 packages/stack-shared/src/config/format.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 4705


🏁 Script executed:

#!/bin/bash
# Check the set function to understand how keys are stored
rg -n "^function set\|^const set" packages/stack-shared/src/utils/objects.ts -A15 | head -50

Repository: stack-auth/stack-auth

Length of output: 147


🏁 Script executed:

#!/bin/bash
# Look at the actual structure of object utilities
sed -n '1,80p' packages/stack-shared/src/utils/objects.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Find where objects utilities are
find packages/stack-shared -name "*objects*" -type f

Repository: stack-auth/stack-auth

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Search for the set function definition
rg -n "^function set\|^const set" packages/stack-shared -A10 | head -50

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Read the objects utilities file
head -150 packages/stack-shared/src/utils/objects.tsx | cat -n

Repository: stack-auth/stack-auth

Length of output: 7697


🏁 Script executed:

#!/bin/bash
# Search for the set function in the tsx file
rg -n "function set|export.*set" packages/stack-shared/src/utils/objects.tsx -A10 | head -80

Repository: stack-auth/stack-auth

Length of output: 848


🏁 Script executed:

#!/bin/bash
# Look at the test more carefully to see what structure is actually passed
sed -n '50,100p' packages/stack-shared/src/config/schema-fuzzer.test.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 2294


🏁 Script executed:

#!/bin/bash
# Check if there's any specific migration test for the customerType inference
rg -n "customerType\|productLines" packages/stack-shared/src/config/schema.ts | grep -A5 -B5 "infer\|migration"

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Read the part of mapProperty and renameProperty that shows they flatten nested objects
sed -n '420,450p' packages/stack-shared/src/config/schema.ts | cat -n

Repository: stack-auth/stack-auth

Length of output: 1873


The customerType inference logic will not work because it assumes flat dot-notation keys but receives nested objects.

After all prior migrations using mapProperty and renameProperty, the config object res maintains its nested structure (e.g., { payments: { products: { "prod-1": { customerType: "user" } } } }). The new inference code iterates Object.entries(res) expecting flat keys like "payments.products.prod-1.customerType", but it only receives top-level keys (["payments"]). The path length check at line 95 requiring path.length === 4 will never be satisfied, so no customer types will be inferred.

Use mapProperty or renameProperty instead to correctly traverse the nested structure, similar to the earlier migrations in this function.

🤖 Prompt for AI Agents
In `@packages/stack-shared/src/config/schema.ts` around lines 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.

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