Skip to content

Comments

feat(webapp): Vercel / Slack integrations improvements#3108

Merged
0ski merged 6 commits intomainfrom
oskar/feat-vercel-improvements-tweaks
Feb 23, 2026
Merged

feat(webapp): Vercel / Slack integrations improvements#3108
0ski merged 6 commits intomainfrom
oskar/feat-vercel-improvements-tweaks

Conversation

@0ski
Copy link
Collaborator

@0ski 0ski commented Feb 20, 2026

Testing

Slack + GitHub + Vercel + Builds + Deployments


Changelog

Settings changes:

  • Split general from integrations
  • Add new Slack section to org level integrations
    Vercel improvements:
  • bugfix for TRIGGER_SECRET_KEY collision
  • onboarding improvements for connecting to projects
  • new loops event
    Slack improvements:
  • nicer alerts
    Webhook/Email alerts:
  • rich events with Github & Vercel integration data

Screenshots

Screenshot 2026-02-20 at 21 53 34 Screenshot 2026-02-23 at 10 55 54 Screenshot 2026-02-20 at 21 52 46 Screenshot 2026-02-20 at 22 04 24 Screenshot 2026-02-19 at 14 48 49 Screenshot 2026-02-19 at 14 49 04 Screenshot 2026-02-19 at 17 32 56 Screenshot 2026-02-20 at 21 57 41 Screenshot 2026-02-19 at 17 33 06

💯

0ski added 5 commits February 20, 2026 22:12
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.
@changeset-bot
Copy link

changeset-bot bot commented Feb 20, 2026

⚠️ No Changeset found

Latest commit: ffcb3a1

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 Feb 20, 2026

Walkthrough

The 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning PR description is missing the issue reference (Closes #). Required checklist items are not present, though testing and changelog sections are provided with substantial content. Add the 'Closes #' reference at the top of the PR description and include the full checklist with check marks for completing each step.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(webapp): Vercel / Slack integrations improvements' clearly and concisely summarizes the main changes across multiple integration improvements for both Vercel and Slack, aligning well with the substantial 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 oskar/feat-vercel-improvements-tweaks

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.

❤️ Share

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

coderabbitai[bot]

This comment was marked as resolved.

@0ski 0ski changed the title oskar/feat-vercel-improvements-tweaks feat(webapp): Vercel / Slack integrations improvements Feb 20, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@0ski 0ski force-pushed the oskar/feat-vercel-improvements-tweaks branch from 2eed28d to ffcb3a1 Compare February 23, 2026 09:46
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 (2)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx (1)

289-329: No empty state when githubAppEnabled is 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 using buildVercelDeploymentUrl helper for consistency.

DeploymentListPresenter uses the shared buildVercelDeploymentUrl helper (which strips the dpl_ prefix), but this presenter constructs the URL inline without stripping. If integrationDeploymentId ever includes the dpl_ 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 typing lastSubmission to remove the as any casts.

The two TODO comments at lines 164 and 180 flag untyped lastSubmission. With @conform-to/react 0.9.x, you can type the action data via the Submission type from @conform-to/react or 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: lastSubmission typed as any — 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 the Submission type 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 overlapping useEffect hooks for Vercel modal state are redundant and hard to reason about.

Effects at lines 215, 237, and 246 all guard hasQueryParam && !isModalOpen and call openVercelOnboarding(). Effect 2 (lines 237–243) is a strict superset of the modal-opening logic in Effects 1 and 3 — if hasQueryParam is true and isModalOpen is 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:

  1. Query param present → load data if needed, then open modal
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2eed28d and ffcb3a1.

📒 Files selected for processing (6)
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.integrations/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.slack.tsx
  • apps/webapp/app/services/projectSettings.server.ts
  • apps/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/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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 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.projects.$projectParam.env.$envParam.settings.general/route.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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/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

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

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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.ts
  • apps/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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
  • apps/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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/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.ts
  • apps/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.ts
  • apps/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 useState from React and path builders from the internal utilities is appropriate.


31-63: Schema design with discriminated union is well-structured.

The createSchema pattern using z.discriminatedUnion with 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 of projectId after membership check.

Both renameProject and deleteProject now use the verified projectId, and error handling with satisfies exhaustiveness checks is a nice pattern.


215-246: Rename form UI is clean — change tracking and loading states handled well.

The controlled hasRenameFormChanges state with onChange comparison and the isRenameLoading derived 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.slug check disables the button, while createSchema with getSlugMatch enforces validation server-side. The danger zone styling and warning hint are clear.

apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts (1)

276-276: vercelDeploymentUrl properly 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: buildVercelDeploymentUrl helper is well-implemented.

The vercelTeamSlug parameter is now correctly typed as string (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. Using typedjson for 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 for triggerConfigFilePath. The useNativeBuildServer checkbox-to-boolean transform is clean.


121-173: Action handler looks correct — membership check before mutation, proper error handling.

The installCommand || undefined pattern at lines 152–154 intentionally converts empty strings to undefined for Prisma, which is consistent with codebase conventions.


347-353: vercelEnvironmentId is 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 action hidden field via the submit button's name/value props.

@0ski 0ski marked this pull request as ready for review February 23, 2026 09:57
Copy link

@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 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

@0ski 0ski enabled auto-merge (squash) February 23, 2026 13:09
@0ski 0ski merged commit 69dc7bc into main Feb 23, 2026
44 checks passed
@0ski 0ski deleted the oskar/feat-vercel-improvements-tweaks branch February 23, 2026 13:48
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.

2 participants