Skip to content

Commit eca32d2

Browse files
committed
chore: merge feat/dashboard-agent-flows (main sync)
2 parents 6a64769 + 54ab14f commit eca32d2

44 files changed

Lines changed: 999 additions & 136 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
The dev environment onboarding now tracks real progress. After you run `init`, the setup checklist marks your project as initialized, and it updates live as your dev server connects and your tasks register. The blank state also adds a "Copy AI agent prompt" button that copies a ready-to-paste setup prompt (pre-filled with your project reference) for Claude Code, Cursor, or any coding agent.
6+
7+
The `init` scaffold now imports from `@trigger.dev/sdk` instead of the deprecated `@trigger.dev/sdk/v3` subpath.

.github/VOUCHED.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ saasjesus
2727
brentshulman-silkline
2828
Leafgard
2929
Rohan170603
30-
NERLOE
30+
NERLOE
31+
Jakub-Vacek
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
Deployment-related API endpoints now draw from their own generous rate limit budget, configurable via the `DEPLOYMENT_RATE_LIMIT_*` environment variables, so runtime API traffic no longer competes with deployments for the same per-environment budget.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Creating an organization sometimes left you back on the creation form even though the organization had already been created, so clicking Create again made a duplicate. Creating an organization now completes and takes you to your new organization.

apps/webapp/app/components/BlankStatePanels.tsx

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
QuestionMarkCircleIcon,
99
RectangleGroupIcon,
1010
RectangleStackIcon,
11+
SparklesIcon,
1112
Squares2X2Icon,
1213
} from "@heroicons/react/20/solid";
1314
import { useLocation } from "react-use";
@@ -36,6 +37,7 @@ import {
3637
} from "~/utils/pathBuilder";
3738
import { AskAgentButton } from "./dashboard-agent/AskAgentButton";
3839
import { CodeBlock } from "./code/CodeBlock";
40+
import { useDevPresence } from "./DevPresence";
3941
import { InlineCode } from "./code/InlineCode";
4042
import { environmentFullTitle, EnvironmentIcon } from "./environments/EnvironmentLabel";
4143
import { Feedback } from "./Feedback";
@@ -54,6 +56,7 @@ import { StepNumber } from "./primitives/StepNumber";
5456
import { TextLink } from "./primitives/TextLink";
5557
import { SimpleTooltip } from "./primitives/Tooltip";
5658
import {
59+
InitAgentPromptV3,
5760
InitCommandV3,
5861
PackageManagerProvider,
5962
TriggerDeployStep,
@@ -111,12 +114,16 @@ function DeployDocsLinks() {
111114
);
112115
}
113116

114-
export function HasNoTasksDev() {
117+
export function HasNoTasksDev({ initializedAt }: { initializedAt: Date | string | null }) {
118+
const { isConnected } = useDevPresence();
119+
const initialized = !!initializedAt;
120+
const devConnected = isConnected === true;
121+
115122
return (
116123
<PackageManagerProvider>
117124
<div>
118125
<div className="mb-6 flex items-center justify-between border-b">
119-
<Header1 spacing>Get setup in 3 minutes</Header1>
126+
<Header1 spacing>Get set up in 2 minutes</Header1>
120127
<div className="flex items-center gap-2">
121128
<Feedback
122129
button={
@@ -128,22 +135,75 @@ export function HasNoTasksDev() {
128135
/>
129136
</div>
130137
</div>
131-
<StepNumber stepNumber="1" title="Run the CLI 'init' command in an existing project" />
132-
<StepContentContainer>
133-
<InitCommandV3 />
134-
<Paragraph spacing>
135-
You'll notice a new folder in your project called{" "}
136-
<InlineCode variant="small">trigger</InlineCode>. We've added a few simple example tasks
137-
in there to help you get started.
138-
</Paragraph>
139-
</StepContentContainer>
140-
<StepNumber stepNumber="2" title="Run the CLI 'dev' command" />
138+
{!initialized && (
139+
<>
140+
<div className="flex flex-col gap-4 rounded-md border border-indigo-400/20 bg-indigo-800/10 p-4 sm:flex-row sm:items-center">
141+
<span className="flex size-9 shrink-0 items-center justify-center rounded-md bg-indigo-500/15 text-indigo-400">
142+
<SparklesIcon className="size-5" />
143+
</span>
144+
<div className="min-w-0 flex-1">
145+
<Paragraph className="text-text-bright">Set it up with your AI agent</Paragraph>
146+
<Paragraph variant="small" className="text-text-dimmed">
147+
Copy a ready-to-paste prompt for Claude Code, Cursor, or any coding agent. It
148+
includes your project reference.
149+
</Paragraph>
150+
</div>
151+
<div className="shrink-0">
152+
<InitAgentPromptV3 />
153+
</div>
154+
</div>
155+
<div className="my-6 flex items-center gap-3">
156+
<div className="h-px flex-1 bg-grid-bright" />
157+
<span className="text-xs uppercase tracking-wide text-text-dimmed">
158+
or set it up yourself
159+
</span>
160+
<div className="h-px flex-1 bg-grid-bright" />
161+
</div>
162+
</>
163+
)}
164+
<StepNumber
165+
stepNumber="1"
166+
title={initialized ? "Project initialized" : "Initialize your project"}
167+
complete={initialized}
168+
/>
141169
<StepContentContainer>
142-
<TriggerDevStepV3 />
170+
{initialized ? (
171+
<Paragraph>
172+
Your project is initialized. Your tasks live in the{" "}
173+
<InlineCode variant="small">trigger</InlineCode> directory.
174+
</Paragraph>
175+
) : (
176+
<>
177+
<InitCommandV3 />
178+
<Paragraph spacing>
179+
Run this in an existing project. You'll notice a new folder called{" "}
180+
<InlineCode variant="small">trigger</InlineCode> with a few example tasks to help
181+
you get started.
182+
</Paragraph>
183+
</>
184+
)}
143185
</StepContentContainer>
144-
<StepNumber stepNumber="3" title="Waiting for tasks" displaySpinner />
186+
<StepNumber
187+
stepNumber="2"
188+
title={devConnected ? "Dev server connected" : "Start the dev server"}
189+
complete={devConnected}
190+
displaySpinner={!devConnected}
191+
/>
145192
<StepContentContainer>
146-
<Paragraph>This page will automatically refresh.</Paragraph>
193+
{devConnected ? (
194+
<Paragraph>
195+
Your dev server is connected. Your tasks will appear here automatically as soon as
196+
they register.
197+
</Paragraph>
198+
) : (
199+
<>
200+
<TriggerDevStepV3 />
201+
<Paragraph spacing>
202+
Keep this running while you develop. Once your tasks register, this page updates
203+
automatically.
204+
</Paragraph>
205+
</>
206+
)}
147207
</StepContentContainer>
148208
</div>
149209
</PackageManagerProvider>

apps/webapp/app/components/SetupCommands.tsx

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { createContext, useContext, useState } from "react";
1+
import { CheckIcon, SparklesIcon } from "@heroicons/react/20/solid";
2+
import { createContext, useContext, useRef, useState } from "react";
23
import { useAppOrigin } from "~/hooks/useAppOrigin";
34
import { useProject } from "~/hooks/useProject";
45
import { useTriggerCliTag } from "~/hooks/useTriggerCliTag";
6+
import { Button } from "./primitives/Buttons";
57
import {
68
ClientTabs,
79
ClientTabsContent,
@@ -10,6 +12,7 @@ import {
1012
} from "./primitives/ClientTabs";
1113
import { ClipboardField } from "./primitives/ClipboardField";
1214
import { Header3 } from "./primitives/Headers";
15+
import { SimpleTooltip } from "./primitives/Tooltip";
1316

1417
type PackageManagerContextType = {
1518
activePackageManager: string;
@@ -36,26 +39,23 @@ function usePackageManager() {
3639
return context;
3740
}
3841

39-
function getApiUrlArg() {
42+
function useApiUrl() {
4043
const appOrigin = useAppOrigin();
4144

42-
let apiUrl: string | undefined = undefined;
43-
4445
switch (appOrigin) {
4546
case "https://cloud.trigger.dev":
46-
// don't display the arg, use the CLI default
47-
break;
47+
return undefined;
4848
case "https://test-cloud.trigger.dev":
49-
apiUrl = "https://test-api.trigger.dev";
50-
break;
49+
return "https://test-api.trigger.dev";
5150
case "https://internal.trigger.dev":
52-
apiUrl = "https://internal-api.trigger.dev";
53-
break;
51+
return "https://internal-api.trigger.dev";
5452
default:
55-
apiUrl = appOrigin;
56-
break;
53+
return appOrigin;
5754
}
55+
}
5856

57+
function getApiUrlArg() {
58+
const apiUrl = useApiUrl();
5959
return apiUrl ? `-a ${apiUrl}` : undefined;
6060
}
6161

@@ -117,6 +117,86 @@ export function InitCommandV3({ title }: TabsProps) {
117117
);
118118
}
119119

120+
function buildAgentSetupPrompt({
121+
projectRef,
122+
apiUrl,
123+
cliTag,
124+
}: {
125+
projectRef: string;
126+
apiUrl: string | undefined;
127+
cliTag: string;
128+
}) {
129+
const apiUrlArg = apiUrl ? ` -a ${apiUrl}` : "";
130+
const apiUrlLine = apiUrl ? `\nTrigger.dev API URL: ${apiUrl}` : "";
131+
132+
return `Set up Trigger.dev in this project.
133+
134+
Trigger.dev runs your background tasks. This is an existing codebase — add Trigger.dev to it and get one task running in the development environment.
135+
136+
Project reference: ${projectRef}${apiUrlLine}
137+
138+
How to do it:
139+
1. If you have the Trigger.dev MCP server available, use its "initialize_project" tool with the project reference above.
140+
2. Otherwise run this and follow its output:
141+
npx trigger.dev@${cliTag} init -p ${projectRef}${apiUrlArg}
142+
3. If you set it up by hand, follow https://trigger.dev/docs/manual-setup and make sure you end up with:
143+
- "@trigger.dev/sdk" installed (latest) and "@trigger.dev/build" as a dev dependency
144+
- a trigger.config.ts with: import { defineConfig } from "@trigger.dev/sdk", project: "${projectRef}", dirs: ["./src/trigger"], and a maxDuration
145+
- a src/trigger/ directory with at least one exported task created with task() from "@trigger.dev/sdk"
146+
- trigger.config.ts added to tsconfig "include", and ".trigger" added to .gitignore
147+
148+
Golden rules:
149+
- Import from "@trigger.dev/sdk". Never "@trigger.dev/sdk/v3" or the deprecated client.defineJob.
150+
- Export every task, including subtasks.
151+
- Use the built-in fetch, not node-fetch.
152+
- Never wrap wait.*, triggerAndWait, or batchTriggerAndWait in Promise.all.
153+
154+
Two steps I have to do myself — ask me when you need them:
155+
- Running "npx trigger.dev@${cliTag} login" (it opens a browser).
156+
- Giving you the development TRIGGER_SECRET_KEY from the dashboard to put in .env.
157+
158+
When you're done, run "npx trigger.dev@${cliTag} dev" and confirm the task shows up in the Trigger.dev dashboard.`;
159+
}
160+
161+
export function InitAgentPromptV3() {
162+
const project = useProject();
163+
const apiUrl = useApiUrl();
164+
const cliTag = useTriggerCliTag();
165+
const [copied, setCopied] = useState(false);
166+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
167+
168+
const onCopy = () => {
169+
const prompt = buildAgentSetupPrompt({
170+
projectRef: project.externalRef,
171+
apiUrl,
172+
cliTag,
173+
});
174+
setCopied(true);
175+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
176+
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
177+
void navigator.clipboard.writeText(prompt).catch(() => {});
178+
};
179+
180+
return (
181+
<SimpleTooltip
182+
asChild
183+
tabbable
184+
button={
185+
<Button
186+
type="button"
187+
variant="primary/medium"
188+
LeadingIcon={copied ? CheckIcon : SparklesIcon}
189+
leadingIconClassName={copied ? "text-success" : undefined}
190+
onClick={onCopy}
191+
>
192+
{copied ? "Copied prompt" : "Copy AI agent prompt"}
193+
</Button>
194+
}
195+
content="Copies a setup prompt to paste into Claude Code, Cursor, or any coding agent"
196+
/>
197+
);
198+
}
199+
120200
export function TriggerDevStepV3({ title }: TabsProps) {
121201
const triggerCliTag = useTriggerCliTag();
122202
const { activePackageManager, setActivePackageManager } = usePackageManager();

apps/webapp/app/entry.server.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ singleton("SentryTenantContextProcessor", () => {
333333

334334
export { apiRateLimiter } from "./services/apiRateLimit.server";
335335
export { dashboardAgentBodyCap } from "./services/dashboardAgentBodyCap.server";
336+
export { deploymentRateLimiter } from "./services/deploymentRateLimit.server";
336337
export { engineRateLimiter } from "./services/engineRateLimit.server";
337338
export { otlpRateLimiter } from "./services/otlpRateLimit.server";
338339
export { runWithHttpContext } from "./services/httpAsyncStorage.server";

apps/webapp/app/env.server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ const EnvironmentSchema = z
240240
CONTROL_PLANE_DATABASE_READ_REPLICA_URL: z.string().optional(),
241241
CONTROL_PLANE_DATABASE_WRITER_DRIVER_ADAPTER: z.string().default("0"),
242242
CONTROL_PLANE_DATABASE_REPLICA_DRIVER_ADAPTER: z.string().default("0"),
243+
CONTROL_PLANE_DEQUEUE_READS_FROM_REPLICA: z.string().default("0"),
243244
RUN_OPS_DATABASE_WRITER_DRIVER_ADAPTER: z.string().default("0"),
244245
RUN_OPS_DATABASE_REPLICA_DRIVER_ADAPTER: z.string().default("0"),
245246
RUN_OPS_LEGACY_DATABASE_WRITER_DRIVER_ADAPTER: z.string().default("0"),
@@ -616,6 +617,14 @@ const EnvironmentSchema = z
616617
API_RATE_LIMIT_JWT_WINDOW: z.string().default("1m"),
617618
API_RATE_LIMIT_JWT_TOKENS: z.coerce.number().int().default(60),
618619

620+
// Separate budget for deploy-flow endpoints, see deploymentRateLimit.server.ts
621+
DEPLOYMENT_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"),
622+
DEPLOYMENT_RATE_LIMIT_MAX: z.coerce.number().int().default(1500),
623+
DEPLOYMENT_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(500),
624+
DEPLOYMENT_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"),
625+
DEPLOYMENT_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"),
626+
DEPLOYMENT_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"),
627+
619628
// Per-IP rate limit for the unauthenticated OTLP ingestion endpoints
620629
// (/otel/*). Bounds unauthenticated request rates. Opt-in
621630
// (disabled by default): because it keys on the source IP, it is only

apps/webapp/app/presenters/v3/TasksStreamPresenter.server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export class TasksStreamPresenter {
8888
safeSend({ data: message.createdAt.toISOString() });
8989
});
9090

91+
subscriber.on("PROJECT_INITIALIZED", async (message) => {
92+
safeSend({ data: message.initializedAt.toISOString() });
93+
});
94+
9195
pinger = setInterval(() => {
9296
if (signal.aborted) {
9397
return close();

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
7272
import { useProject } from "~/hooks/useProject";
7373
import { useSearchParams } from "~/hooks/useSearchParam";
7474
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
75+
import { prisma } from "~/db.server";
7576
import { findProjectBySlug } from "~/models/project.server";
7677
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
7778
import {
@@ -134,7 +135,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
134135

135136
const usefulLinksPreference = await getUsefulLinksPreference(request);
136137

137-
return typeddefer({ items, hourlyActivity, runningStates, usefulLinksPreference });
138+
const initialized = await prisma.project.findFirst({
139+
where: { id: project.id },
140+
select: { initializedAt: true },
141+
});
142+
143+
return typeddefer({
144+
items,
145+
hourlyActivity,
146+
runningStates,
147+
usefulLinksPreference,
148+
projectInitializedAt: initialized?.initializedAt ?? null,
149+
});
138150
} catch (error) {
139151
console.error(error);
140152
throw new Response(undefined, {
@@ -201,7 +213,7 @@ export default function Page() {
201213
const organization = useOrganization();
202214
const project = useProject();
203215
const environment = useEnvironment();
204-
const { items, hourlyActivity, runningStates, usefulLinksPreference } =
216+
const { items, hourlyActivity, runningStates, usefulLinksPreference, projectInitializedAt } =
205217
useTypedLoaderData<typeof loader>();
206218
const { value, values } = useSearchParams();
207219

@@ -354,7 +366,7 @@ export default function Page() {
354366
</div>
355367
) : environment.type === "DEVELOPMENT" ? (
356368
<MainCenteredContainer className="max-w-prose">
357-
<HasNoTasksDev />
369+
<HasNoTasksDev initializedAt={projectInitializedAt} />
358370
</MainCenteredContainer>
359371
) : (
360372
<MainCenteredContainer className="max-w-prose">

0 commit comments

Comments
 (0)