Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d7efcb8
feat: prompt management with service layer, dashboard UI, and AI span…
ericallam Mar 20, 2026
3c696b7
feat: operations/providers filters, prompt version model display, ove…
ericallam Mar 20, 2026
86beeb9
feat: generations/metrics across all versions by default, add version…
ericallam Mar 20, 2026
db26f63
feat: prompts list redesign with separate columns, bar sparklines, ve…
ericallam Mar 20, 2026
8d5b28f
feat: full-width override banner, cleaner header for prompt detail page
ericallam Mar 21, 2026
1a81db4
feat: add timestamp and duration to AI span inspector header
ericallam Mar 21, 2026
37e3367
feat: custom span inspectors for top-level AI SDK spans (generateText…
ericallam Mar 21, 2026
3935f7e
fix: a11y label on expand button, try-catch request.json, add missing…
ericallam Mar 21, 2026
7a681c2
fix: TypeScript strict mode errors across AI prompts feature
ericallam Mar 21, 2026
2228c9e
coderabbit fixes
ericallam Mar 22, 2026
96e1426
fix: transaction safety, parsePeriodToMs consistency, SQL injection p…
ericallam Mar 22, 2026
a2adc21
fix: parameterize prompt slug in SQL queries, add ownership check to …
ericallam Mar 22, 2026
f78675f
fix: replace SQL string interpolation with parameterized promptVersio…
ericallam Mar 22, 2026
6586bc6
fix: move distinct operations/providers queries to PromptPresenter, p…
ericallam Mar 22, 2026
f267bba
fix: increase generations polling interval from 5s to 10s
ericallam Mar 22, 2026
dc7f1ad
fix: remove unused private methods from PromptService
ericallam Mar 22, 2026
543bf2c
refactor: extract shared AI span helpers, SpanMetricRow, parsePeriodT…
ericallam Mar 22, 2026
251e46f
fix: wrap prompt version label removal + creation in transaction to p…
ericallam Mar 22, 2026
dcdad1e
fixed 2 validateDOMNesting console errors
ericallam Mar 22, 2026
ceb6391
fix: pre-fill override dialog model from current override instead of …
ericallam Mar 22, 2026
3e63357
fix: use $transaction helper instead of prisma.$transaction for Prism…
ericallam Mar 22, 2026
224af6f
chore: add changeset and server-changes for AI prompt management
ericallam Mar 22, 2026
5ba393a
fix: include prompt entity type in isAiInspector to avoid nested scro…
ericallam Mar 22, 2026
8cd61cf
fix: override edit dialog uses override version content instead of se…
ericallam Mar 22, 2026
7533941
fix: add createMultiMethodApiRoute helper, rewrite override API with …
ericallam Mar 22, 2026
4f396c7
feat: add prompt management SDK methods (list, versions, resolve, ove…
ericallam Mar 22, 2026
e583e5b
fix: promptHandle.resolve() always uses API when client is available,…
ericallam Mar 22, 2026
7a5d9d9
feat: add tracing spans to all prompt management SDK methods
ericallam Mar 22, 2026
ad226d4
fix: remove codepath accessory from prompts.list() span
ericallam Mar 22, 2026
5865db8
feat: typesafe prompts.resolve<typeof myPrompt>() with PromptIdentifi…
ericallam Mar 22, 2026
410e397
fix: use != null check in resolveVersion to handle version 0 correctly
ericallam Mar 22, 2026
c5f88fe
fix: use UTC arithmetic for sparkline bucket keys to avoid timezone m…
ericallam Mar 22, 2026
4cf009a
refactor: migrate MCP prompt tools from raw fetchClient to typed API …
ericallam Mar 22, 2026
9084f98
fix: use z.coerce.number() for MCP prompt version inputs to handle st…
ericallam Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ai-prompt-management.md
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 30 additions & 0 deletions .server-changes/ai-prompt-management.md
Original file line number Diff line number Diff line change
@@ -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 `<div>` inside `<p>` DOM nesting warnings in span titles and chat messages
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ pnpm run dev --filter webapp # Run webapp (http://localhost:3030)
pnpm run dev --filter trigger.dev --filter "@trigger.dev/*" # Watch CLI and packages
```

### Verifying Changes

The verification command depends on where the change lives:

- **Apps and internal packages** (`apps/*`, `internal-packages/*`): Use `typecheck`. **Never use `build`** for these — building proves almost nothing about correctness.
- **Public packages** (`packages/*`): Use `build`.

```bash
# Apps and internal packages — use typecheck
pnpm run typecheck --filter webapp # ~1-2 minutes
pnpm run typecheck --filter @internal/run-engine

# Public packages — use build
pnpm run build --filter @trigger.dev/sdk
pnpm run build --filter @trigger.dev/core
```

Only run typecheck/build 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.
Expand Down
42 changes: 42 additions & 0 deletions apps/webapp/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@

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. The webapp is an app, not a public package — use 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.

Note: Public packages (`packages/*`) use `build` instead. See the root CLAUDE.md for details.

## 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`
Expand Down
54 changes: 54 additions & 0 deletions apps/webapp/app/components/BlankStatePanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -686,3 +688,55 @@ function DeploymentOnboardingSteps() {
</PackageManagerProvider>
);
}

export function PromptsNone() {
return (
<InfoPanel
title="Define your first prompt"
icon={SparklesIcon}
iconClassName="text-purple-500"
panelClassName="max-w-lg"
accessory={
<LinkButton to={docsPath("prompt-management")} variant="docs/small" LeadingIcon={BookOpenIcon}>
Prompt docs
</LinkButton>
}
>
<Paragraph spacing variant="small">
Managed prompts let you define AI prompts in code with typesafe variables, then edit and
version them from the dashboard without redeploying.
</Paragraph>
<Paragraph spacing variant="small">
Add a prompt to your project using <InlineCode variant="small">prompts.define()</InlineCode>:
</Paragraph>
<div className="rounded border border-grid-dimmed bg-charcoal-900 p-3">
<pre className="text-xs leading-relaxed text-text-dimmed">
<span className="text-purple-400">import</span>
{" { prompts } "}
<span className="text-purple-400">from</span>
{' "@trigger.dev/sdk";\n'}
<span className="text-purple-400">import</span>
{" { z } "}
<span className="text-purple-400">from</span>
{' "zod";\n\n'}
<span className="text-purple-400">export const</span>
{" myPrompt = "}
<span className="text-blue-400">prompts.define</span>
{"({\n"}
{" id: "}
<span className="text-green-400">"my-prompt"</span>
{",\n"}
{" variables: z.object({\n"}
{" name: z.string(),\n"}
{" }),\n"}
{" content: "}
<span className="text-green-400">{"`Hello {{name}}!`"}</span>
{",\n"});</pre>
</div>
<Paragraph variant="small" className="mt-2">
Deploy your project and your prompts will appear here with version history and a live
editor.
</Paragraph>
</InfoPanel>
);
}
29 changes: 25 additions & 4 deletions apps/webapp/app/components/code/TSQLResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,12 @@ function CellValueWrapper({
value,
column,
prettyFormatting,
row,
}: {
value: unknown;
column: OutputColumnMetadata;
prettyFormatting: boolean;
row?: Record<string, unknown>;
}) {
const [hovered, setHovered] = useState(false);

Expand All @@ -478,6 +480,7 @@ function CellValueWrapper({
column={column}
prettyFormatting={prettyFormatting}
hovered={hovered}
row={row}
/>
</span>
);
Expand All @@ -491,11 +494,13 @@ function CellValue({
column,
prettyFormatting = true,
hovered = false,
row,
}: {
value: unknown;
column: OutputColumnMetadata;
prettyFormatting?: boolean;
hovered?: boolean;
row?: Record<string, unknown>;
}) {
// Plain text mode - render everything as monospace text with truncation
if (!prettyFormatting) {
Expand Down Expand Up @@ -562,12 +567,20 @@ function CellValue({
switch (column.customRenderType) {
case "runId": {
if (typeof value === "string") {
const spanId = row?.["span_id"];
const runPath = v3RunPathFromFriendlyId(value);
const href = typeof spanId === "string" && spanId
? `${runPath}?span=${spanId}`
: runPath;
const tooltip = typeof spanId === "string" && spanId
? "Jump to span"
: "Jump to run";
return (
<SimpleTooltip
content="Jump to run"
content={tooltip}
disableHoverableContent
hidden={!hovered}
button={<TextLink to={v3RunPathFromFriendlyId(value)}>{value}</TextLink>}
button={<TextLink to={href}>{value}</TextLink>}
/>
);
}
Expand Down Expand Up @@ -1010,13 +1023,16 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
prettyFormatting = true,
sorting: defaultSorting = [],
showHeaderOnEmpty = false,
hiddenColumns,
}: {
rows: Record<string, unknown>[];
columns: OutputColumnMetadata[];
prettyFormatting?: boolean;
sorting?: SortingState;
/** When true, show column headers + "No results" on empty data. When false, show a blank state icon. */
showHeaderOnEmpty?: boolean;
/** Column names to hide from display but keep in row data (useful for linking) */
hiddenColumns?: string[];
}) {
const tableContainerRef = useRef<HTMLDivElement>(null);

Expand All @@ -1030,9 +1046,13 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({

// Create TanStack Table column definitions from OutputColumnMetadata
// Calculate column widths based on content
const visibleColumns = useMemo(
() => hiddenColumns?.length ? columns.filter((col) => !hiddenColumns.includes(col.name)) : columns,
[columns, hiddenColumns]
);
const columnDefs = useMemo<ColumnDef<RowData, unknown>[]>(
() =>
columns.map((col) => ({
visibleColumns.map((col) => ({
id: col.name,
accessorKey: col.name,
header: () => col.name,
Expand All @@ -1041,6 +1061,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
value={info.getValue()}
column={col}
prettyFormatting={prettyFormatting}
row={info.row.original}
/>
),
meta: {
Expand All @@ -1050,7 +1071,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
size: calculateColumnWidth(col.name, rows, col),
filterFn: fuzzyFilter,
})),
[columns, rows, prettyFormatting]
[visibleColumns, rows, prettyFormatting]
);

// Initialize TanStack Table
Expand Down
Loading
Loading