Skip to content

feat(webapp): self serve preview branches and team members#3201

Open
samejr wants to merge 21 commits intomainfrom
feat(webapp)-self-serve-preview-branches
Open

feat(webapp): self serve preview branches and team members#3201
samejr wants to merge 21 commits intomainfrom
feat(webapp)-self-serve-preview-branches

Conversation

@samejr
Copy link
Member

@samejr samejr commented Mar 10, 2026

Adds 2 self serve features

1. self serve preview branches

  • Copies the patterns of the self serve concurrency
  • Self serve only available on Pro plan (otherwise you are linked to the billing plans page)
  • Global self serve branches limit: 180 (+20 for the Pro plan). It can be overridden per Org
  • You need to archive branches before reducing the number of extra branches you're paying for
  • Branches are removed immediately but remain billed until the end of the billing cycle like extra concurrency

2. self serve team members

  • Copies the patterns of the self serve concurrency
  • Self serve only available on Pro plan (otherwise you are linked to the billing plans page)
  • Global self serve members is unlimited but can be limited with the same env var quota and overridden per org if needed
  • You need to remove team members before reducing the number of members you pay for
  • Team members are removed immediately but remain billed until the end of the billing cycle like extra concurrency

@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

⚠️ No Changeset found

Latest commit: bcbb07c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a generic SearchInput component (renamed from LogsSearchInput) with an optional resetParams prop to reset specified query keys on submit/clear. Branches and Team presenters now fetch current plan and plans in parallel and surface purchase-related fields (canPurchaseBranches/canPurchaseSeats, extraBranches/extraSeats, branchPricing/seatPricing, max quotas, plan limits). Introduces SetBranchesAddOnService and SetSeatsAddOnService plus platform helpers setBranchesAddOn/setSeatsAddOn. Adds purchase flows and UI (PurchaseBranchesModal, PurchaseSeatsModal), updates routes, loaders, and actions to support purchases/quota requests, and adjusts branch/seat limit logic and related UI/styling; removes some console logging.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is missing required template sections: Testing and Changelog are not provided, and issue reference is absent. Add the missing Testing section describing steps taken to test both self-serve features, provide a Changelog with a concise summary of changes, and include the issue number reference (Closes #).
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(webapp): self serve preview branches and team members' accurately summarizes the two main features added in the changeset.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat(webapp)-self-serve-preview-branches

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.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

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

🧹 Nitpick comments (2)
apps/webapp/app/v3/services/setSeatsAddOn.server.ts (1)

2-2: Use a @trigger.dev/core subpath import here.

This new root import violates the webapp package-boundary rule; the same pattern was also added in apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx.

As per coding guidelines, apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/services/setSeatsAddOn.server.ts` at line 2, Replace the
root import of tryCatch from "@trigger.dev/core" with the approved package
subpath export defined in `@trigger.dev/core`'s package.json (use the
webapp-approved subpath, e.g. the server/runtime subpath provided by the
package) so the webapp package-boundary rule is respected; update the import
that brings in tryCatch (the symbol tryCatch in setSeatsAddOn.server.ts) to
import from the proper "@trigger.dev/core/..." subpath and do the same change in
the other occurrence noted (route.tsx) to keep imports consistent.
apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx (1)

37-37: Extract PurchaseSeatsModal into a shared component module.

Importing a presentational component from a route module that owns loaders, actions, and server-only imports (db.server, session.server, member.server, message.server, setSeatsAddOn.server) creates a brittle cross-route dependency. Move the modal to a dedicated component or presenter module under ~/components/ or ~/presenters/ instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.invite/route.tsx at line
37, The PurchaseSeatsModal component is being imported from a route module that
includes server-only logic, creating a brittle cross-route dependency; extract
PurchaseSeatsModal into a pure presentational module (e.g.,
~/components/PurchaseSeatsModal or ~/presenters/PurchaseSeatsModal) so it no
longer pulls in loaders/actions or server-only imports. Move the JSX and any
client-side handlers into the new file, export a prop-driven component (no
direct
db.server/session.server/member.server/message.server/setSeatsAddOn.server
usage), update both the existing import in
_app.orgs.$organizationSlug.settings.team/route and the import in
_app.orgs.$organizationSlug.invite/route to import the new shared module, and
adapt callers to pass necessary data/handlers (or create small thin adapter
wrappers in their routes) so server interactions remain in the route files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.team/route.tsx:
- Around line 624-625: The form's submit target is hardcoded to
organizationTeamPath(organization) in PurchaseSeatsModal via fetcher.Form; make
the form action configurable by adding an action prop (e.g., action?: string) to
PurchaseSeatsModal and using that prop for fetcher.Form's action, defaulting to
organizationTeamPath(organization) when no action is provided, then update the
invite route (_app.orgs.$organizationSlug.invite/route.tsx) to pass the invite
flow's post target into PurchaseSeatsModal so successful purchases return to the
invite flow instead of redirecting to the team page.
- Around line 542-554: The icon-only submit Button (LeadingIcon={NoSymbolIcon})
lacks an accessible name; add an accessible name to the Button used inside
SimpleTooltip so screen readers announce its purpose (e.g., add
aria-label="Revoke invite" or include a visually-hidden text child). Update the
Button props in the SimpleTooltip child (the Button component) to provide that
label so the control is accessible while keeping the icon and tooltip.
- Around line 101-110: PurchaseSchema currently only enforces minimum numeric
values and the route handler forwards the parsed amount directly to the service,
allowing crafted POSTs to bypass business rules enforced in PurchaseSeatsModal;
update the server-side validation and enforcement: (1) tighten PurchaseSchema to
require whole-seat integers (use z.coerce.number().int() and appropriate .min()
per action) and (2) add explicit checks in the route POST handler (the code that
parses PurchaseSchema and calls the service) to enforce plan eligibility,
prevent non-whole-seat quantities, and when reducing seats require removal of
members/invites or reject the request—only after these checks pass call the
existing service method (the same call currently forwarding amount) to apply the
change.

In `@apps/webapp/app/v3/services/setSeatsAddOn.server.ts`:
- Around line 25-29: The service's call method performs billing mutations
(setSeatsAddOn) and may create quota requests without checking that userId is
authorized for organizationId; before calling setSeatsAddOn or sending any
quota-increase request, validate that userId belongs to organizationId and has
billing/admin rights (e.g., call your existing org auth helpers such as
requireUserInOrganization or requireOrgAdmin/requireBillingPermission) and
return a 403/throw if unauthorized; insert this check at the top of the async
call({ userId, organizationId, ... }) function so both the setSeatsAddOn branch
and the quota-request branch (lines ~57–87) are protected.

---

Nitpick comments:
In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.invite/route.tsx:
- Line 37: The PurchaseSeatsModal component is being imported from a route
module that includes server-only logic, creating a brittle cross-route
dependency; extract PurchaseSeatsModal into a pure presentational module (e.g.,
~/components/PurchaseSeatsModal or ~/presenters/PurchaseSeatsModal) so it no
longer pulls in loaders/actions or server-only imports. Move the JSX and any
client-side handlers into the new file, export a prop-driven component (no
direct
db.server/session.server/member.server/message.server/setSeatsAddOn.server
usage), update both the existing import in
_app.orgs.$organizationSlug.settings.team/route and the import in
_app.orgs.$organizationSlug.invite/route to import the new shared module, and
adapt callers to pass necessary data/handlers (or create small thin adapter
wrappers in their routes) so server interactions remain in the route files.

In `@apps/webapp/app/v3/services/setSeatsAddOn.server.ts`:
- Line 2: Replace the root import of tryCatch from "@trigger.dev/core" with the
approved package subpath export defined in `@trigger.dev/core`'s package.json (use
the webapp-approved subpath, e.g. the server/runtime subpath provided by the
package) so the webapp package-boundary rule is respected; update the import
that brings in tryCatch (the symbol tryCatch in setSeatsAddOn.server.ts) to
import from the proper "@trigger.dev/core/..." subpath and do the same change in
the other occurrence noted (route.tsx) to keep imports consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fe290cba-f28e-4dec-a4d2-ad756323a56d

📥 Commits

Reviewing files that changed from the base of the PR and between 2e28389 and a03061e.

📒 Files selected for processing (5)
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/services/platform.v3.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/services/platform.v3.server.ts
📜 Review details
⏰ 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). (28)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: In TypeScript SDK usage, always import from @trigger.dev/sdk, never from @trigger.dev/sdk/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
apps/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/, etc.) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
apps/webapp/**/*.server.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Access environment variables via the env export from app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/presenters/TeamPresenter.server.ts
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
apps/webapp/app/v3/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Organize services in the webapp following the pattern app/v3/services/*/*.server.ts

Files:

  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
apps/webapp/app/v3/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In apps/webapp/app/v3/ directory, only modify V2 code paths - V1/V2 branching should only have V2 modified, as V1 code is legacy

Files:

  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
apps/webapp/app/v3/services/**/*.server.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

When editing services that branch on RunEngineVersion to support both V1 and V2 (e.g., cancelTaskRun.server.ts, batchTriggerV3.server.ts), only modify V2 code paths

Files:

  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
🧠 Learnings (6)
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/presenters/TeamPresenter.server.ts
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx

Comment on lines +101 to +110
const PurchaseSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("purchase"),
amount: z.coerce.number().min(0, "Amount must be 0 or more"),
}),
z.object({
action: z.literal("quota-increase"),
amount: z.coerce.number().min(1, "Amount must be greater than 0"),
}),
]);
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 | 🟠 Major

Don’t rely on the modal state to enforce seat-purchase rules.

Line 104/108 only validate a minimum, and Lines 138-145 forward the parsed amount straight to the service. Right now the checks for plan eligibility, whole-seat quantities, and “remove members/invites before reducing seats” live in PurchaseSeatsModal, so a crafted POST can bypass this route’s own business rules.

Also applies to: 132-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.team/route.tsx
around lines 101 - 110, PurchaseSchema currently only enforces minimum numeric
values and the route handler forwards the parsed amount directly to the service,
allowing crafted POSTs to bypass business rules enforced in PurchaseSeatsModal;
update the server-side validation and enforcement: (1) tighten PurchaseSchema to
require whole-seat integers (use z.coerce.number().int() and appropriate .min()
per action) and (2) add explicit checks in the route POST handler (the code that
parses PurchaseSchema and calls the service) to enforce plan eligibility,
prevent non-whole-seat quantities, and when reducing seats require removal of
members/invites or reject the request—only after these checks pass call the
existing service method (the same call currently forwarding amount) to apply the
change.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.team/route.tsx:
- Around line 126-149: The org lookup before calling SetSeatsAddOnService lacks
an authorization check, so any authenticated user can change seats by slug;
before constructing/awaiting service.call in the "purchase-seats" branch, verify
the user's membership and billing-admin role for the resolved org (reuse the
existing org-access check logic used elsewhere, e.g., the helper that validates
membership/roles) or delegate this enforcement into SetSeatsAddOnService; ensure
you perform the check using userId and org.id (the same values passed to
service.call), and return an appropriate json({ ok: false, error: ... }) when
the user is not authorized.
- Line 13: Update the import for tryCatch to use the utilities subpath: replace
the root import of tryCatch (import { tryCatch } from "@trigger.dev/core") with
the subpath import (import { tryCatch } from "@trigger.dev/core/utils") in the
module that defines the route (the file containing the tryCatch import for this
org settings team route).
- Around line 573-583: The prop type for triggerButton is too permissive
(React.ReactNode) and can allow fragments, strings, arrays, or null which break
Radix's Slot used by DialogTrigger with asChild; update the prop signature to
require a single element by changing triggerButton?: React.ReactNode to
triggerButton?: React.ReactElement (or non-optional React.ReactElement if
required) in the component props where seatPricing, extraSeats, usedSeats,
maxQuota, planSeatLimit and triggerButton are defined, ensuring consumers pass
exactly one React element to DialogTrigger with asChild.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9b11b9fe-7abf-4e50-ad87-90c646f94261

📥 Commits

Reviewing files that changed from the base of the PR and between a03061e and 0bb5fc1.

📒 Files selected for processing (1)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📜 Review details
⏰ 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). (27)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: In TypeScript SDK usage, always import from @trigger.dev/sdk, never from @trigger.dev/sdk/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
apps/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/, etc.) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
🧠 Learnings (5)
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx

Comment on lines +126 to +149
if (formType === "purchase-seats") {
const org = await $replica.organization.findFirst({
where: { slug: organizationSlug },
select: { id: true },
});

if (!org) {
return json({ ok: false, error: "Organization not found" } as const);
}

const submission = parse(formData, { schema: PurchaseSchema });

if (!submission.value || submission.intent !== "submit") {
return json(submission);
}

const service = new SetSeatsAddOnService();
const [error, result] = await tryCatch(
service.call({
userId,
organizationId: org.id,
action: submission.value.action,
amount: submission.value.amount,
})
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

Authorize seat changes before calling the billing service.

This branch only resolves the org by slug and then forwards organizationId into SetSeatsAddOnService. The new service path does not enforce membership or billing-admin checks either, so any authenticated user who knows another org's slug can change its seat add-on or trigger a quota request. Please gate this with the same org-access check used elsewhere, or move the authorization check into SetSeatsAddOnService so every caller is protected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.team/route.tsx
around lines 126 - 149, The org lookup before calling SetSeatsAddOnService lacks
an authorization check, so any authenticated user can change seats by slug;
before constructing/awaiting service.call in the "purchase-seats" branch, verify
the user's membership and billing-admin role for the resolved org (reuse the
existing org-access check logic used elsewhere, e.g., the helper that validates
membership/roles) or delegate this enforcement into SetSeatsAddOnService; ensure
you perform the check using userId and org.id (the same values passed to
service.call), and return an appropriate json({ ok: false, error: ... }) when
the user is not authorized.

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.

♻️ Duplicate comments (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx (1)

342-349: ⚠️ Potential issue | 🟡 Minor

Guard against division by zero in progress calculations.

If limits.limit is ever 0 (e.g., edge case during data loading or misconfigured plan), these calculations would produce Infinity or NaN, causing incorrect SVG rendering.

🛡️ Proposed fix
               <circle
                 className={`fill-none ${requiresUpgrade ? "stroke-error" : "stroke-success"}`}
                 strokeWidth="4"
                 r="10"
                 cx="12"
                 cy="12"
-                strokeDasharray={`${(limits.used / limits.limit) * 62.8} 62.8`}
+                strokeDasharray={`${limits.limit > 0 ? (limits.used / limits.limit) * 62.8 : 0} 62.8`}
                 strokeDashoffset="0"
                 strokeLinecap="round"
               />
             </svg>
           </div>
         }
-        content={`${Math.round((limits.used / limits.limit) * 100)}%`}
+        content={`${limits.limit > 0 ? Math.round((limits.used / limits.limit) * 100) : 0}%`}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.team/route.tsx
around lines 342 - 349, The progress calculations use limits.used / limits.limit
directly which can produce Infinity/NaN when limits.limit is 0; update the
expressions in the SVG strokeDasharray and the content percent (references:
strokeDasharray={`${(limits.used / limits.limit) * 62.8} 62.8`} and
content={`${Math.round((limits.used / limits.limit) * 100)}%`}) to guard against
division-by-zero by using a safe denominator (e.g., denom = limits.limit > 0 ?
limits.limit : 1), compute the ratio = Math.min(Math.max(limits.used / denom,
0), 1) and then use ratio * 62.8 for strokeDasharray and Math.round(ratio * 100)
+ '%' for content so the SVG and displayed percent remain valid.
🧹 Nitpick comments (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx (1)

655-655: Remove unused variable.

The organization variable is declared but never used in this component.

🧹 Proposed fix
 function PurchaseBranchesModal({
   ...
 }) {
   const lastSubmission = useActionData();
-  const organization = useOrganization();
   const [form, { amount }] = useForm({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
at line 655, The variable "organization" is declared via useOrganization() but
never used; remove the unused declaration to clean up the component—delete the
line containing "const organization = useOrganization();" (or if you intended to
use it, reference "organization" where needed); ensure no other code depends on
useOrganization() in this file (search for useOrganization and organization)
before removing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.team/route.tsx:
- Around line 342-349: The progress calculations use limits.used / limits.limit
directly which can produce Infinity/NaN when limits.limit is 0; update the
expressions in the SVG strokeDasharray and the content percent (references:
strokeDasharray={`${(limits.used / limits.limit) * 62.8} 62.8`} and
content={`${Math.round((limits.used / limits.limit) * 100)}%`}) to guard against
division-by-zero by using a safe denominator (e.g., denom = limits.limit > 0 ?
limits.limit : 1), compute the ratio = Math.min(Math.max(limits.used / denom,
0), 1) and then use ratio * 62.8 for strokeDasharray and Math.round(ratio * 100)
+ '%' for content so the SVG and displayed percent remain valid.

---

Nitpick comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx:
- Line 655: The variable "organization" is declared via useOrganization() but
never used; remove the unused declaration to clean up the component—delete the
line containing "const organization = useOrganization();" (or if you intended to
use it, reference "organization" where needed); ensure no other code depends on
useOrganization() in this file (search for useOrganization and organization)
before removing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 53ed5d07-80d8-4b21-bd60-31c124dd1422

📥 Commits

Reviewing files that changed from the base of the PR and between 0bb5fc1 and b6bcd0e.

📒 Files selected for processing (3)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/v3/services/setSeatsAddOn.server.ts
📜 Review details
⏰ 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). (25)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: In TypeScript SDK usage, always import from @trigger.dev/sdk, never from @trigger.dev/sdk/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
apps/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/, etc.) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
🧠 Learnings (24)
📓 Common learnings
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3200
File: docs/config/config-file.mdx:353-368
Timestamp: 2026-03-10T12:44:19.869Z
Learning: In the triggerdotdev/trigger.dev repository, docs PRs are often written as companions to implementation PRs (e.g., PR `#3200` documents features being added in PR `#3196`). When reviewing docs PRs, the documented features may exist in a companion/companion PR branch rather than main. Always check companion PRs referenced in the PR description before flagging missing implementations.
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2026-03-10T17:56:26.581Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3201
File: apps/webapp/app/v3/services/setSeatsAddOn.server.ts:25-29
Timestamp: 2026-03-10T17:56:26.581Z
Learning: In the `triggerdotdev/trigger.dev` webapp, service classes such as `SetSeatsAddOnService` and `SetBranchesAddOnService` do NOT need to perform their own userId-to-organizationId authorization checks. Auth is enforced at the route layer: `requireUserId(request)` authenticates the user, and the `_app.orgs.$organizationSlug` layout route enforces that the authenticated user is a member of the org. Any `userId` and `organizationId` reaching these services from org-scoped routes are already validated. This is the consistent pattern used across all org-scoped services in the codebase.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-02-11T16:50:14.167Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx:126-131
Timestamp: 2026-02-11T16:50:14.167Z
Learning: In apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx, MetricsDashboard entities are intentionally scoped to the organization level, not the project level. The dashboard lookup should filter by organizationId only (not projectId), allowing dashboards to be accessed across projects within the same organization. The optional projectId field on MetricsDashboard serves other purposes and should not be used as an authorization constraint.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-03-02T12:43:37.906Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/core/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:37.906Z
Learning: Applies to packages/core/**/*.{ts,tsx,js,jsx} : Never import the root package (trigger.dev/core). Always use subpath imports such as trigger.dev/core/v3, trigger.dev/core/v3/utils, trigger.dev/core/logger, or trigger.dev/core/schemas

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to **/*.{ts,tsx} : Import from trigger.dev/core subpaths only, never from the root

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-03-02T12:43:48.124Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to apps/webapp/app/v3/**/*.{ts,tsx} : In apps/webapp/app/v3/ directory, only modify V2 code paths - V1/V2 branching should only have V2 modified, as V1 code is legacy

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to **/*.{ts,tsx} : In TypeScript SDK usage, always import from trigger.dev/sdk, never from trigger.dev/sdk/v3 or use deprecated client.defineJob

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-01-28T16:57:47.620Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 2964
File: apps/webapp/app/components/AskAI.tsx:121-141
Timestamp: 2026-01-28T16:57:47.620Z
Learning: In the trigger.dev webapp codebase, the Button component (apps/webapp/app/components/primitives/Buttons) does not spread unknown props to the underlying <button> element—it only passes specific props (type, disabled, onClick, name, value, ref, form, autoFocus). When using Radix UI's TooltipTrigger with asChild, a span wrapper around the Button is necessary to receive Radix props (aria-describedby, onPointerEnter, onPointerLeave, data-state) while the Button handles its own behavior. Directly making the Button the child of TooltipTrigger with asChild will break accessibility and tooltip functionality.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-02-19T18:09:23.944Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3095
File: apps/webapp/app/components/navigation/DashboardDialogs.tsx:47-47
Timestamp: 2026-02-19T18:09:23.944Z
Learning: In the triggerdotdev/trigger.dev codebase, the pattern `isPaying === false` is used consistently to explicitly check for free plan users. This is a project convention that distinguishes between `isPaying === true` (paying), `isPaying === false` (free), and `isPaying === undefined` (no subscription data). Do not suggest changing this to negation pattern.
```
<!-- <review_comment_addressed>

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-03-02T12:43:02.539Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: Applies to docs/**/*.mdx : Always import from `trigger.dev/sdk` in code examples (never from `trigger.dev/sdk/v3`)

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
📚 Learning: 2025-07-12T18:00:06.163Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/utils/searchParams.ts:16-18
Timestamp: 2025-07-12T18:00:06.163Z
Learning: The `objectToSearchParams` function in `apps/webapp/app/utils/searchParams.ts` is used to generate URL parameters from objects and is separate from code that parses incoming search parameters. Changes to this function only affect places where it's used to create URLs, not places that parse search parameters from external sources.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx
🔇 Additional comments (15)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx (6)

1-63: LGTM!

Imports are correctly structured. The @trigger.dev/core/utils subpath import for tryCatch aligns with project conventions.


129-166: Authorization is already enforced at the layout layer.

The past review flagged a missing auth check here, but per the codebase pattern, the _app.orgs.$organizationSlug layout route already enforces that the authenticated user (from requireUserId) is a member of the organization. This is the consistent pattern used across all org-scoped services. Based on learnings from this repository, service classes such as SetSeatsAddOnService do NOT need to perform their own userId-to-organizationId authorization checks.


486-544: LGTM!

The cooldown implementation is well-structured with proper cleanup. The initial cooldown calculation from invite.updatedAt prevents spam after page refresh, and the interval-based countdown with cleanup is correctly implemented.


546-569: LGTM!

The accessibility concern from past reviews has been addressed with aria-label="Revoke invite" on line 560.


571-588: LGTM!

The triggerButton prop type has been correctly updated to React.ReactElement to satisfy Radix's DialogTrigger with asChild, addressing the previous review concern.


811-834: LGTM!

The state logic correctly handles all seat change scenarios with proper boundary checks. The string union return type aligns with the project's preference for string unions over enums.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx (9)

1-80: LGTM!

Imports are well-organized and compliant with coding guidelines. The @trigger.dev/core/v3 subpath import is correctly used.


128-137: LGTM!

The discriminated union schema cleanly separates the two action types with appropriate validation constraints.


139-191: LGTM!

The purchase-branches action handler properly validates input, handles errors from the service layer, and provides appropriate success/error feedback. The use of tryCatch and discriminated error handling is clean.


226-529: LGTM!

The page component cleanly integrates the new purchase flow with appropriate conditional rendering. The guard canPurchaseBranches && branchPricing ensures the modal only renders when pricing data is available.


531-558: LGTM!

The refactored BranchFilters component correctly uses SearchInput with resetParams to handle pagination reset. The useCallback with an empty dependency array is fine since setSearchParams is stable.


560-634: LGTM!

The UpgradePanel component cleanly handles the branching logic between purchase modal and upgrade dialog based on plan capabilities.


636-866: Overall implementation is solid.

The modal correctly handles multiple purchase states (increase, decrease, above_quota, need_to_archive) with appropriate UI feedback and pricing calculations. The summary breakdown is clear and user-friendly.


868-891: LGTM!

The state calculation logic correctly handles all edge cases. The ordering of checks ensures need_to_archive is checked before returning decrease, and above_quota only applies to increases.


893-977: LGTM!

The NewBranchPanel component remains unchanged and correctly handles the branch creation flow.

@samejr samejr marked this pull request as ready for review March 10, 2026 18:38
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant