Skip to content

Commit c14bfa7

Browse files
committed
feat(webapp,cli,database): track real dev onboarding progress
The dev environment "Get set up" panel now reflects actual progress instead of static instructions. Running trigger init records the project as initialized (via a new project-scoped endpoint the CLI calls), so step 1 ticks off, and the panel updates live as the dev server connects and tasks register. Adds a "Copy AI agent prompt" button that copies a ready-to-paste setup prompt pre-filled with the project reference, and updates the init scaffold to import from @trigger.dev/sdk instead of the deprecated /v3 subpath.
1 parent 820c079 commit c14bfa7

17 files changed

Lines changed: 306 additions & 36 deletions

File tree

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.

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 { AskAI } from "./AskAI";
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,
@@ -62,12 +65,16 @@ import {
6265
import { StepContentContainer } from "./StepContentContainer";
6366
import { V4Badge } from "./V4Badge";
6467

65-
export function HasNoTasksDev() {
68+
export function HasNoTasksDev({ initializedAt }: { initializedAt: Date | string | null }) {
69+
const { isConnected } = useDevPresence();
70+
const initialized = !!initializedAt;
71+
const devConnected = isConnected === true;
72+
6673
return (
6774
<PackageManagerProvider>
6875
<div>
6976
<div className="mb-6 flex items-center justify-between border-b">
70-
<Header1 spacing>Get setup in 3 minutes</Header1>
77+
<Header1 spacing>Get set up in 2 minutes</Header1>
7178
<div className="flex items-center gap-2">
7279
<Feedback
7380
button={
@@ -79,22 +86,75 @@ export function HasNoTasksDev() {
7986
/>
8087
</div>
8188
</div>
82-
<StepNumber stepNumber="1" title="Run the CLI 'init' command in an existing project" />
83-
<StepContentContainer>
84-
<InitCommandV3 />
85-
<Paragraph spacing>
86-
You'll notice a new folder in your project called{" "}
87-
<InlineCode variant="small">trigger</InlineCode>. We've added a few simple example tasks
88-
in there to help you get started.
89-
</Paragraph>
90-
</StepContentContainer>
91-
<StepNumber stepNumber="2" title="Run the CLI 'dev' command" />
89+
{!initialized && (
90+
<>
91+
<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">
92+
<span className="flex size-9 shrink-0 items-center justify-center rounded-md bg-indigo-500/15 text-indigo-400">
93+
<SparklesIcon className="size-5" />
94+
</span>
95+
<div className="min-w-0 flex-1">
96+
<Paragraph className="text-text-bright">Set it up with your AI agent</Paragraph>
97+
<Paragraph variant="small" className="text-text-dimmed">
98+
Copy a ready-to-paste prompt for Claude Code, Cursor, or any coding agent. It
99+
includes your project reference.
100+
</Paragraph>
101+
</div>
102+
<div className="shrink-0">
103+
<InitAgentPromptV3 />
104+
</div>
105+
</div>
106+
<div className="my-6 flex items-center gap-3">
107+
<div className="h-px flex-1 bg-grid-bright" />
108+
<span className="text-xs uppercase tracking-wide text-text-dimmed">
109+
or set it up yourself
110+
</span>
111+
<div className="h-px flex-1 bg-grid-bright" />
112+
</div>
113+
</>
114+
)}
115+
<StepNumber
116+
stepNumber="1"
117+
title={initialized ? "Project initialized" : "Initialize your project"}
118+
complete={initialized}
119+
/>
92120
<StepContentContainer>
93-
<TriggerDevStepV3 />
121+
{initialized ? (
122+
<Paragraph>
123+
Your project is initialized. Your tasks live in the{" "}
124+
<InlineCode variant="small">trigger</InlineCode> directory.
125+
</Paragraph>
126+
) : (
127+
<>
128+
<InitCommandV3 />
129+
<Paragraph spacing>
130+
Run this in an existing project. You'll notice a new folder called{" "}
131+
<InlineCode variant="small">trigger</InlineCode> with a few example tasks to help
132+
you get started.
133+
</Paragraph>
134+
</>
135+
)}
94136
</StepContentContainer>
95-
<StepNumber stepNumber="3" title="Waiting for tasks" displaySpinner />
137+
<StepNumber
138+
stepNumber="2"
139+
title={devConnected ? "Dev server connected" : "Start the dev server"}
140+
complete={devConnected}
141+
displaySpinner={!devConnected}
142+
/>
96143
<StepContentContainer>
97-
<Paragraph>This page will automatically refresh.</Paragraph>
144+
{devConnected ? (
145+
<Paragraph>
146+
Your dev server is connected. Your tasks will appear here automatically as soon as
147+
they register.
148+
</Paragraph>
149+
) : (
150+
<>
151+
<TriggerDevStepV3 />
152+
<Paragraph spacing>
153+
Keep this running while you develop. Once your tasks register, this page updates
154+
automatically.
155+
</Paragraph>
156+
</>
157+
)}
98158
</StepContentContainer>
99159
</div>
100160
</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/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
@@ -71,6 +71,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
7171
import { useProject } from "~/hooks/useProject";
7272
import { useSearchParams } from "~/hooks/useSearchParam";
7373
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
74+
import { prisma } from "~/db.server";
7475
import { findProjectBySlug } from "~/models/project.server";
7576
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
7677
import {
@@ -128,7 +129,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
128129

129130
const usefulLinksPreference = await getUsefulLinksPreference(request);
130131

131-
return typeddefer({ items, hourlyActivity, runningStates, usefulLinksPreference });
132+
const initialized = await prisma.project.findFirst({
133+
where: { id: project.id },
134+
select: { initializedAt: true },
135+
});
136+
137+
return typeddefer({
138+
items,
139+
hourlyActivity,
140+
runningStates,
141+
usefulLinksPreference,
142+
projectInitializedAt: initialized?.initializedAt ?? null,
143+
});
132144
} catch (error) {
133145
console.error(error);
134146
throw new Response(undefined, {
@@ -195,7 +207,7 @@ export default function Page() {
195207
const organization = useOrganization();
196208
const project = useProject();
197209
const environment = useEnvironment();
198-
const { items, hourlyActivity, runningStates, usefulLinksPreference } =
210+
const { items, hourlyActivity, runningStates, usefulLinksPreference, projectInitializedAt } =
199211
useTypedLoaderData<typeof loader>();
200212
const { value, values } = useSearchParams();
201213

@@ -355,7 +367,7 @@ export default function Page() {
355367
</div>
356368
) : environment.type === "DEVELOPMENT" ? (
357369
<MainCenteredContainer className="max-w-prose">
358-
<HasNoTasksDev />
370+
<HasNoTasksDev initializedAt={projectInitializedAt} />
359371
</MainCenteredContainer>
360372
) : (
361373
<MainCenteredContainer className="max-w-prose">

0 commit comments

Comments
 (0)