feat(webapp): Vercel / Slack integrations improvements#3108
Conversation
Redirect project settings to the general settings route - Replace v3ProjectSettingsPath with v3ProjectSettingsGeneralPath in org project settings loader so users land on the correct general settings page after selecting an environment. Add integrations settings route for Vercel onboarding flows - Swap v3ProjectSettingsPath for v3ProjectSettingsIntegrationsPath in VercelOnboardingModal imports to point onboarding actions at the integrations-specific settings route. Improve VercelOnboardingModal behavior and telemetry - Introduce origin variable and use it to derive fromMarketplaceContext for clearer intent. - Add optional vercelManageAccessUrl prop to allow rendering a manage access link when present. - Track a "vercel onboarding github step viewed" event with extra context (origin, step, org/project slugs, GH app installation state) and include capture and identifiers in the effect dependencies. - Prevent dialog from closing when interacting outside by handling onInteractOutside to stop accidental dismissals. - Start introducing rendering for a "Manage access" link next to the Connect Project button when vercelManageAccessUrl exists and no origin query param is present. Misc - Update effect dependency array to include newly referenced values (capture, organizationSlug, projectSlug, gitHubAppInstallations.length) to satisfy hooks correctness.
Add resolution and propagation of deployment integration metadata (git links and Vercel deployment URL) to alert delivery flow so emails, Slack messages and webhooks can include richer context for deployment alerts. - Resolve Git metadata and Vercel integration info via BranchesPresenter and Vercel integration schema in a new #resolveDeploymentMetadata flow; represent result as DeploymentIntegrationMetadata. - Thread DeploymentIntegrationMetadata through DeliverAlertService and pass it into #sendEmail, #sendSlack and #sendWebhook calls. - Populate email payload with git details (branch, short SHA, commit message and PR info) and vercelDeploymentUrl when present. - Use neverthrow.fromPromise to safely handle metadata resolution and fall back to empty metadata on errors. Motivation: provide recipients immediate links and SCM context for deployment success/failure alerts to speed debugging and navigation.
Replace the generic "Integrations" item with a dedicated Vercel entry in the Organization Settings side menu. Import the VercelLogo component and adjust the menu structure to include an "Integrations" header and a separate Vercel menu item. Update icon props and active color to match the Vercel logo styling. This clarifies the integrations section and provides a direct link to the organization's Vercel integration page.
Add a fire-and-forget call to loopsClient.vercelIntegrationStarted from the Vercel installation route so we emit a "vercel-integration" event when a user begins the Vercel integration flow. Implement vercelIntegrationStarted in services/loops.server to log and dispatch the event payload (email, userId, firstName, eventName). This provides visibility into integration adoption without blocking the redirect flow. Error handling is intentionally silent to avoid affecting user experience.
Introduce a new route for organization Slack integration settings, including loader and action handlers. The loader fetches the organization's Slack integration, its team name, and related alert channels for display. The action supports an "uninstall" intent that disables related Slack alert channels and marks the integration as deleted within a single transaction. Also add UI imports and helper utilities used by the page (formatDate, components, and validation). Include error handling and logging for missing integrations and transaction failures. Reasons: - Provide a dedicated settings UI for viewing Slack integration details and managing/uninstalling the integration. - Ensure uninstall cleans up related alert channels atomically to avoid inconsistent state.
|
WalkthroughThe PR splits project settings into "General" and "Integrations" paths, adds path-builder helpers, and introduces a VercelLink React component. It threads Vercel data (vercelDeploymentUrl and vercelManageAccessUrl) through presenters and presenters/services, adds a private Vercel env-var removal helper, and emits Vercel install telemetry via LoopsClient. New routes/pages include Integrations settings, General settings, Slack integration management, Vercel install/connect flows, and a Vercel onboarding modal. Email/webhook schemas and templates now include optional git metadata and Vercel deployment information. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
2eed28d to
ffcb3a1
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (2)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx (1)
289-329: No empty state whengithubAppEnabledis false — page renders blank.When the GitHub app is not enabled, the entire integration panel (Git settings, Vercel, Build settings) is hidden, but there's no fallback message guiding the user. This leaves a visually empty page.
Consider rendering an informational message or call-to-action when
!githubAppEnabled.🤖 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.settings.integrations/route.tsx around lines 289 - 329, The UI hides all integration panels when githubAppEnabled is false, leaving a blank page; update the JSX around the githubAppEnabled conditional to render a clear empty state or CTA when !githubAppEnabled (e.g., an informational message and a button/link to enable the GitHub app or go to integration settings). Locate the block guarded by the githubAppEnabled check (containing GitHubSettingsPanel, VercelSettingsPanel, BuildSettingsForm and props like organization.slug, project.slug, environment.slug, handleOpenVercelModal, vercelFetcher) and replace the hidden branch with a visible fallback component or markup that explains the integrations are disabled and provides an action to enable/configure them.apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts (1)
165-208: Inline Vercel URL construction — consider usingbuildVercelDeploymentUrlhelper for consistency.
DeploymentListPresenteruses the sharedbuildVercelDeploymentUrlhelper (which strips thedpl_prefix), but this presenter constructs the URL inline without stripping. IfintegrationDeploymentIdever includes thedpl_prefix, the resulting URL will be incorrect here but correct in the list presenter.Using the helper keeps behavior consistent:
♻️ Proposed fix
+import { VercelProjectIntegrationDataSchema, buildVercelDeploymentUrl } from "~/v3/vercel/vercelProjectIntegrationSchema"; -import { VercelProjectIntegrationDataSchema } from "~/v3/vercel/vercelProjectIntegrationSchema";if (integrationDeployment) { - const vercelId = integrationDeployment.integrationDeploymentId; - vercelDeploymentUrl = `https://vercel.com/${parsed.data.vercelTeamSlug}/${parsed.data.vercelProjectName}/${vercelId}`; + vercelDeploymentUrl = buildVercelDeploymentUrl( + parsed.data.vercelTeamSlug, + parsed.data.vercelProjectName, + integrationDeployment.integrationDeploymentId + ); }Based on learnings: "Consider centralizing this logic in a small helper (e.g., getVercelDeploymentId(id) or a URL builder) and add tests to verify both prefixed and non-prefixed inputs."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts` around lines 165 - 208, Replace the inline Vercel URL construction in DeploymentPresenter.server (the block that reads parsed = VercelProjectIntegrationDataSchema.safeParse(...) and constructs vercelDeploymentUrl using integrationDeployment.integrationDeploymentId) with a call to the shared buildVercelDeploymentUrl helper (or a small helper that strips the "dpl_" prefix) so behavior matches DeploymentListPresenter; specifically, pass parsed.data.vercelTeamSlug, parsed.data.vercelProjectName and the integrationDeployment.integrationDeploymentId into buildVercelDeploymentUrl (or first normalize the id with getVercelDeploymentId) and assign its result to vercelDeploymentUrl, ensuring prefixed ids are handled consistently and covered by tests.
🧹 Nitpick comments (3)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx (1)
162-165: Consider typinglastSubmissionto remove theas anycasts.The two TODO comments at lines 164 and 180 flag untyped
lastSubmission. With@conform-to/react0.9.x, you can type the action data via theSubmissiontype from@conform-to/reactor infer it from the parse result. Low priority, but worth tracking.Also applies to: 178-181
🤖 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.settings.general/route.tsx around lines 162 - 165, The lastSubmission value passed into useForm is currently typed as any; replace the cast by typing lastSubmission using the Submission<T> type from `@conform-to/react` (or derive its type from your parse result) and update the useForm call so the state tuple const [renameForm, { projectName }] = useForm<YourFormDataType>({ id: "rename-project", lastSubmission }); ensure you import Submission from `@conform-to/react` and replace YourFormDataType with the concrete shape you parse/expect for renameProject so both the renameForm and the other useForm call at lines ~178-181 are strongly typed instead of using as any.apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx (2)
360-390:lastSubmissiontyped asany— same pattern as the general settings route.Line 361 casts
useActionData() as any. Same TODO-worthy item as in the general settings page. Consider adding a shared type or using theSubmissiontype from@conform-to/react.🤖 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.settings.integrations/route.tsx around lines 360 - 390, The code currently casts useActionData() to any for lastSubmission in BuildSettingsForm; replace the any with a proper Submission type (or a shared form submission type used across settings pages) so the useForm call gets typed correctly—import Submission from "@conform-to/react" (or reference your shared type) and type lastSubmission as Submission<typeof UpdateBuildSettingsFormSchema> (or the equivalent shared type) before passing it into useForm in BuildSettingsForm to remove the any cast and ensure type-safe validation with UpdateBuildSettingsFormSchema.
213-253: Three overlappinguseEffecthooks for Vercel modal state are redundant and hard to reason about.Effects at lines 215, 237, and 246 all guard
hasQueryParam && !isModalOpenand callopenVercelOnboarding(). Effect 2 (lines 237–243) is a strict superset of the modal-opening logic in Effects 1 and 3 — ifhasQueryParamis true andisModalOpenis false, Effect 2 will always open the modal regardless of data state. This makes the open-modal branches in Effects 1 and 3 dead code and adds cognitive overhead.Consider consolidating into a single effect that handles:
- Query param present → load data if needed, then open modal
- Query param removed → close modal
♻️ Sketch of a consolidated effect
- useEffect(() => { - if (hasQueryParam && vercelIntegrationEnabled) { - if (vercelFetcher.data?.onboardingData && vercelFetcher.state === "idle") { - if (!isModalOpen) { - openVercelOnboarding(); - } - } else if (vercelFetcher.state === "idle" && vercelFetcher.data === undefined) { - vercelFetcher.load( - `${vercelResourcePath(organization.slug, project.slug, environment.slug)}?vercelOnboarding=true` - ); - } - } else if (!hasQueryParam && isModalOpen) { - setIsModalOpen(false); - } - }, [hasQueryParam, vercelIntegrationEnabled, organization.slug, project.slug, environment.slug, vercelFetcher.data, vercelFetcher.state, isModalOpen, openVercelOnboarding]); - - useEffect(() => { - if (hasQueryParam && !isModalOpen) { - openVercelOnboarding(); - } - }, [hasQueryParam, isModalOpen, openVercelOnboarding]); - - useEffect(() => { - if (hasQueryParam && vercelFetcher.data?.onboardingData && vercelFetcher.state === "idle") { - if (!isModalOpen) { - openVercelOnboarding(); - } - } - }, [hasQueryParam, vercelFetcher.data, vercelFetcher.state, isModalOpen, openVercelOnboarding]); + // Single effect: manage modal open/close based on query param and data readiness + useEffect(() => { + if (!hasQueryParam) { + if (isModalOpen) setIsModalOpen(false); + return; + } + if (!vercelIntegrationEnabled) return; + + // Ensure data is loading or loaded + if (vercelFetcher.state === "idle" && vercelFetcher.data === undefined) { + vercelFetcher.load( + `${vercelResourcePath(organization.slug, project.slug, environment.slug)}?vercelOnboarding=true` + ); + return; + } + + // Open modal when query param is present (data may or may not be loaded yet) + if (!isModalOpen) { + openVercelOnboarding(); + } + }, [hasQueryParam, vercelIntegrationEnabled, organization.slug, project.slug, environment.slug, vercelFetcher.data, vercelFetcher.state, isModalOpen, openVercelOnboarding]);🤖 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.settings.integrations/route.tsx around lines 213 - 253, Consolidate the three overlapping useEffect hooks into one effect that watches hasQueryParam, vercelIntegrationEnabled, organization.slug, project.slug, environment.slug, vercelFetcher.data, vercelFetcher.state, isModalOpen and openVercelOnboarding; inside it, if hasQueryParam && vercelIntegrationEnabled then (a) trigger vercelFetcher.load(...) using vercelResourcePath(...) when vercelFetcher.state is "idle" and vercelFetcher.data is undefined, and (b) ensure the modal is open by calling openVercelOnboarding() when vercelFetcher.data?.onboardingData is present or if the modal is currently closed, otherwise if !hasQueryParam and isModalOpen call setIsModalOpen(false); remove the other two effects so the modal-opening branches in the original effects are not duplicated.
🤖 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/presenters/v3/DeploymentPresenter.server.ts`:
- Around line 165-208: Replace the inline Vercel URL construction in
DeploymentPresenter.server (the block that reads parsed =
VercelProjectIntegrationDataSchema.safeParse(...) and constructs
vercelDeploymentUrl using integrationDeployment.integrationDeploymentId) with a
call to the shared buildVercelDeploymentUrl helper (or a small helper that
strips the "dpl_" prefix) so behavior matches DeploymentListPresenter;
specifically, pass parsed.data.vercelTeamSlug, parsed.data.vercelProjectName and
the integrationDeployment.integrationDeploymentId into buildVercelDeploymentUrl
(or first normalize the id with getVercelDeploymentId) and assign its result to
vercelDeploymentUrl, ensuring prefixed ids are handled consistently and covered
by tests.
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx:
- Around line 289-329: The UI hides all integration panels when githubAppEnabled
is false, leaving a blank page; update the JSX around the githubAppEnabled
conditional to render a clear empty state or CTA when !githubAppEnabled (e.g.,
an informational message and a button/link to enable the GitHub app or go to
integration settings). Locate the block guarded by the githubAppEnabled check
(containing GitHubSettingsPanel, VercelSettingsPanel, BuildSettingsForm and
props like organization.slug, project.slug, environment.slug,
handleOpenVercelModal, vercelFetcher) and replace the hidden branch with a
visible fallback component or markup that explains the integrations are disabled
and provides an action to enable/configure them.
---
Nitpick comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx:
- Around line 162-165: The lastSubmission value passed into useForm is currently
typed as any; replace the cast by typing lastSubmission using the Submission<T>
type from `@conform-to/react` (or derive its type from your parse result) and
update the useForm call so the state tuple const [renameForm, { projectName }] =
useForm<YourFormDataType>({ id: "rename-project", lastSubmission }); ensure you
import Submission from `@conform-to/react` and replace YourFormDataType with the
concrete shape you parse/expect for renameProject so both the renameForm and the
other useForm call at lines ~178-181 are strongly typed instead of using as any.
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx:
- Around line 360-390: The code currently casts useActionData() to any for
lastSubmission in BuildSettingsForm; replace the any with a proper Submission
type (or a shared form submission type used across settings pages) so the
useForm call gets typed correctly—import Submission from "@conform-to/react" (or
reference your shared type) and type lastSubmission as Submission<typeof
UpdateBuildSettingsFormSchema> (or the equivalent shared type) before passing it
into useForm in BuildSettingsForm to remove the any cast and ensure type-safe
validation with UpdateBuildSettingsFormSchema.
- Around line 213-253: Consolidate the three overlapping useEffect hooks into
one effect that watches hasQueryParam, vercelIntegrationEnabled,
organization.slug, project.slug, environment.slug, vercelFetcher.data,
vercelFetcher.state, isModalOpen and openVercelOnboarding; inside it, if
hasQueryParam && vercelIntegrationEnabled then (a) trigger
vercelFetcher.load(...) using vercelResourcePath(...) when vercelFetcher.state
is "idle" and vercelFetcher.data is undefined, and (b) ensure the modal is open
by calling openVercelOnboarding() when vercelFetcher.data?.onboardingData is
present or if the modal is currently closed, otherwise if !hasQueryParam and
isModalOpen call setIsModalOpen(false); remove the other two effects so the
modal-opening branches in the original effects are not duplicated.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.slack.tsxapps/webapp/app/services/projectSettings.server.tsapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.slack.tsx
- apps/webapp/app/services/projectSettings.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). (24)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 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 / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: sdk-compat / Deno Runtime
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
- GitHub Check: sdk-compat / Cloudflare Workers
- GitHub Check: typecheck / typecheck
🧰 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}: Always import tasks from@trigger.dev/sdk, never use@trigger.dev/sdk/v3or deprecatedclient.defineJobpattern
Every Trigger.dev task must be exported and have a uniqueidproperty with no timeouts in the run function
Files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Import from
@trigger.dev/coreusing subpaths only, never import from root
Files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
apps/webapp/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Access all environment variables through the
envexport ofenv.server.tsinstead of directly accessingprocess.envin the Trigger.dev webapp
Files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
apps/webapp/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
apps/webapp/**/*.{ts,tsx}: When importing from@trigger.dev/corein 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 webappAccess environment variables via
envexport fromapps/webapp/app/env.server.ts, never useprocess.envdirectly
Files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
🧠 Learnings (16)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
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).
📚 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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
📚 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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.
Applied to files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/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.projects.$projectParam.env.$envParam.settings.general/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
📚 Learning: 2026-02-06T19:53:46.849Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts:233-237
Timestamp: 2026-02-06T19:53:46.849Z
Learning: Vercel dashboard URLs for deployments use the format `https://vercel.com/${teamSlug}/${projectName}/${deploymentId}` where the deploymentId should NOT include the `dpl_` prefix. When constructing Vercel dashboard links from API deployment IDs (which include the `dpl_` prefix), the prefix must be stripped using `.replace(/^dpl_/, "")`.
Applied to files:
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
📚 Learning: 2026-02-03T18:27:05.229Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
Applied to files:
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.tsapps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
📚 Learning: 2025-08-14T10:09:02.528Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: internal-packages/run-engine/src/engine/index.ts:466-467
Timestamp: 2025-08-14T10:09:02.528Z
Learning: In the triggerdotdev/trigger.dev codebase, it's acceptable to pass `string | undefined` types directly to Prisma operations (both create and update). The codebase consistently uses this pattern and the team is comfortable with how Prisma handles undefined values.
Applied to files:
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
📚 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/v3/DeploymentPresenter.server.ts
📚 Learning: 2026-02-06T19:53:38.843Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts:233-237
Timestamp: 2026-02-06T19:53:38.843Z
Learning: When constructing Vercel dashboard URLs from deployment IDs, always strip the dpl_ prefix from the ID. Implement this by transforming the ID with .replace(/^dpl_/, "") before concatenating into the URL: https://vercel.com/${teamSlug}/${projectName}/${cleanedDeploymentId}. Consider centralizing this logic in a small helper (e.g., getVercelDeploymentId(id) or a URL builder) and add tests to verify both prefixed and non-prefixed inputs.
Applied to files:
apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
📚 Learning: 2025-11-14T19:24:39.536Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2685
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:1234-1257
Timestamp: 2025-11-14T19:24:39.536Z
Learning: In the trigger.dev project, version validation for the `useNativeBuildServer` setting cannot be performed at the settings form level because the SDK version is only known at build/deployment time, not when saving project settings.
Applied to files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
📚 Learning: 2025-09-05T15:32:41.553Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/services/projectSettings.server.ts:210-0
Timestamp: 2025-09-05T15:32:41.553Z
Learning: In the ProjectSettingsService.updateGitSettings method in apps/webapp/app/services/projectSettings.server.ts, when productionBranch or stagingBranch parameters are undefined, the method intentionally writes empty objects to clear existing branch configurations. This is the desired behavior, not a bug.
Applied to files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
📚 Learning: 2024-09-17T16:37:27.060Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 1313
File: apps/webapp/app/hooks/useSearchParam.ts:21-21
Timestamp: 2024-09-17T16:37:27.060Z
Learning: When using `URLSearchParams.set` followed by `.toString()`, values are automatically URL-encoded. Manually encoding these values can lead to double encoding issues.
Applied to files:
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
🧬 Code graph analysis (1)
apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts (1)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (1)
VercelProjectIntegrationDataSchema(67-75)
🔇 Additional comments (12)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx (5)
1-29: Imports and setup look good.Clean separation of concerns with proper Remix, conform, and zod imports. Using
useStatefrom React and path builders from the internal utilities is appropriate.
31-63: Schema design with discriminated union is well-structured.The
createSchemapattern usingz.discriminatedUnionwith deferred constraint injection (for slug matching) is a clean approach for handling multiple form actions with different validation rules in a single route.
65-153: Action handler is solid — consistent use ofprojectIdafter membership check.Both
renameProjectanddeleteProjectnow use the verifiedprojectId, and error handling withsatisfiesexhaustiveness checks is a nice pattern.
215-246: Rename form UI is clean — change tracking and loading states handled well.The controlled
hasRenameFormChangesstate withonChangecomparison and theisRenameLoadingderived state properly gate the submit button. Good UX pattern.
250-288: Delete form with dual validation (button disable + server-side schema) is solid.The client-side
deleteInputValue !== project.slugcheck disables the button, whilecreateSchemawithgetSlugMatchenforces validation server-side. The danger zone styling and warning hint are clear.apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts (1)
276-276:vercelDeploymentUrlproperly threaded into the return value.Clean integration — the URL is optional and only set when all prerequisites (integration data, team slug, deployment record) are met.
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (1)
205-212:buildVercelDeploymentUrlhelper is well-implemented.The
vercelTeamSlugparameter is now correctly typed asstring(addressing the previous review), and the anchored regex^dpl_safely handles both prefixed and non-prefixed deployment IDs.apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx (5)
42-83: Loader is well-structured with proper error handling.Authentication, param validation via
EnvironmentParamSchema, and the result-pattern error handling are clean. Usingtypedjsonfor typed loader data is appropriate.
85-119: Build settings schema validation is thorough.Good use of
.trim(), newline restrictions, length limits, and leading-slash normalization fortriggerConfigFilePath. TheuseNativeBuildServercheckbox-to-boolean transform is clean.
121-173: Action handler looks correct — membership check before mutation, proper error handling.The
installCommand || undefinedpattern at lines 152–154 intentionally converts empty strings toundefinedfor Prisma, which is consistent with codebase conventions.
347-353:vercelEnvironmentIdis now properly URL-encoded — previous review addressed.The
encodeURIComponent(vercelEnvironmentId)at line 350 correctly handles special characters.
396-499: Build settings form implementation is clean and functional.Change tracking, field validation, loading states, and the checkbox-to-boolean pattern are all well-handled. The form correctly uses the
actionhidden field via the submit button'sname/valueprops.
Testing
Slack + GitHub + Vercel + Builds + Deployments
Changelog
Settings changes:
Vercel improvements:
Slack improvements:
Webhook/Email alerts:
Screenshots
💯