diff --git a/.changeset/ai-prompt-management.md b/.changeset/ai-prompt-management.md
new file mode 100644
index 00000000000..d3250bebda7
--- /dev/null
+++ b/.changeset/ai-prompt-management.md
@@ -0,0 +1,5 @@
+---
+"@trigger.dev/sdk": patch
+---
+
+Define and manage AI prompts with `prompts.define()`. Create typesafe prompt templates with variables, resolve them at runtime, and manage versions and overrides from the dashboard without redeploying.
diff --git a/.server-changes/ai-prompt-management.md b/.server-changes/ai-prompt-management.md
new file mode 100644
index 00000000000..624ec391047
--- /dev/null
+++ b/.server-changes/ai-prompt-management.md
@@ -0,0 +1,30 @@
+---
+area: webapp
+type: feature
+---
+
+AI prompt management dashboard and enhanced span inspectors.
+
+**Prompt management:**
+- Prompts list page with version status, model, override indicators, and 24h usage sparklines
+- Prompt detail page with template viewer, variable preview, version history timeline, and override editor
+- Create, edit, and remove overrides to change prompt content or model without redeploying
+- Promote any code-deployed version to current
+- Generations tab with infinite scroll, live polling, and inline span inspector
+- Per-prompt metrics: total generations, avg tokens, avg cost, latency, with version-level breakdowns
+
+**AI span inspectors:**
+- Custom inspectors for `ai.generateText`, `ai.streamText`, `ai.generateObject`, `ai.streamObject` parent spans
+- `ai.toolCall` inspector showing tool name, call ID, and input arguments
+- `ai.embed` inspector showing model, provider, and input text
+- Prompt tab on AI spans linking to prompt version with template and input variables
+- Compact timestamp and duration header on all AI span inspectors
+
+**AI metrics dashboard:**
+- Operations, Providers, and Prompts filters on the AI Metrics dashboard
+- Cost by prompt widget
+- "AI" section in the sidebar with Prompts and AI Metrics links
+
+**Other improvements:**
+- Resizable panel sizes now persist across page refreshes
+- Fixed `
` inside `
` DOM nesting warnings in span titles and chat messages
diff --git a/CLAUDE.md b/CLAUDE.md
index 688ef4d8d80..68ae918f5f7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -18,6 +18,16 @@ pnpm run dev --filter webapp # Run webapp (http://localhost:3030)
pnpm run dev --filter trigger.dev --filter "@trigger.dev/*" # Watch CLI and packages
```
+### Verifying Webapp Changes
+
+**Never run `pnpm run build --filter webapp` to verify changes.** Building proves almost nothing about correctness. Instead, run typecheck from the repo root:
+
+```bash
+pnpm run typecheck --filter webapp # ~1-2 minutes
+```
+
+Only run typecheck after major changes (new files, significant refactors, schema changes). For small edits, trust the types and let CI catch issues.
+
## Testing
We use vitest exclusively. **Never mock anything** - use testcontainers instead.
diff --git a/apps/webapp/CLAUDE.md b/apps/webapp/CLAUDE.md
index d1533e9808a..6f245ae6a9d 100644
--- a/apps/webapp/CLAUDE.md
+++ b/apps/webapp/CLAUDE.md
@@ -2,6 +2,46 @@
Remix 2.1.0 app serving as the main API, dashboard, and orchestration engine. Uses an Express server (`server.ts`).
+## Verifying Changes
+
+**Never run `pnpm run build --filter webapp` to verify changes.** Building proves almost nothing about correctness. Instead, run typecheck from the repo root:
+
+```bash
+pnpm run typecheck --filter webapp # ~1-2 minutes
+```
+
+Only run typecheck after major changes (new files, significant refactors, schema changes). For small edits, trust the types and let CI catch issues.
+
+## Testing Dashboard Changes with Chrome DevTools MCP
+
+Use the `chrome-devtools` MCP server to visually verify local dashboard changes. The webapp must be running (`pnpm run dev --filter webapp` from repo root).
+
+### Login
+
+```
+1. mcp__chrome-devtools__new_page(url: "http://localhost:3030")
+ → Redirects to /login
+2. mcp__chrome-devtools__click the "Continue with Email" link
+3. mcp__chrome-devtools__fill the email field with "local@trigger.dev"
+4. mcp__chrome-devtools__click "Send a magic link"
+ → Auto-logs in and redirects to the dashboard (no email verification needed locally)
+```
+
+### Navigating and Verifying
+
+- **take_snapshot**: Get an a11y tree of the page (text content, element UIDs for interaction). Prefer this over screenshots for understanding page structure.
+- **take_screenshot**: Capture what the page looks like visually. Use to verify styling, layout, and visual changes.
+- **navigate_page**: Go to specific URLs, e.g. `http://localhost:3030/orgs/references-bc08/projects/hello-world-SiWs/env/dev/runs`
+- **click / fill**: Interact with elements using UIDs from `take_snapshot`.
+- **evaluate_script**: Run JS in the browser console for debugging.
+- **list_console_messages**: Check for console errors after navigating.
+
+### Tips
+
+- Snapshots can be very large on complex pages (200K+ chars). Use `take_screenshot` first to orient, then `take_snapshot` only when you need element UIDs to interact.
+- The local seeded user email is `local@trigger.dev`.
+- Dashboard URL pattern: `http://localhost:3030/orgs/{orgSlug}/projects/{projectSlug}/env/{envSlug}/{section}`
+
## Key File Locations
- **Trigger API**: `app/routes/api.v1.tasks.$taskId.trigger.ts`
diff --git a/apps/webapp/app/components/BlankStatePanels.tsx b/apps/webapp/app/components/BlankStatePanels.tsx
index 7423bd61ac8..111e4efebb8 100644
--- a/apps/webapp/app/components/BlankStatePanels.tsx
+++ b/apps/webapp/app/components/BlankStatePanels.tsx
@@ -4,11 +4,13 @@ import {
BookOpenIcon,
ChatBubbleLeftRightIcon,
ClockIcon,
+ DocumentTextIcon,
PlusIcon,
QuestionMarkCircleIcon,
RectangleGroupIcon,
RectangleStackIcon,
ServerStackIcon,
+ SparklesIcon,
Squares2X2Icon,
} from "@heroicons/react/20/solid";
import { useLocation } from "react-use";
@@ -686,3 +688,55 @@ function DeploymentOnboardingSteps() {
);
}
+
+export function PromptsNone() {
+ return (
+
+ Prompt docs
+
+ }
+ >
+
+ Managed prompts let you define AI prompts in code with typesafe variables, then edit and
+ version them from the dashboard without redeploying.
+
+
+ Add a prompt to your project using prompts.define():
+
+
+
+
+ Generations appear here when this prompt version is resolved inside a task using{" "}
+ prompt.resolve() with an AI SDK call.
+
+
+ Try adjusting the time period filter or switching to a different version.
+
+
+