Add TTL (time-to-live) defaults at task-level and config-level, with precedence: per-trigger > task > config > dev default (10m). Docs PR: #3200 (merge after packages are released)
6.6 KiB
Webapp
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:
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_screenshotfirst to orient, thentake_snapshotonly 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 - Batch trigger:
app/routes/api.v1.tasks.batch.ts - OTEL endpoints:
app/routes/otel.v1.logs.ts,app/routes/otel.v1.traces.ts - Prisma setup:
app/db.server.ts - Run engine config:
app/v3/runEngine.server.ts - Services:
app/v3/services/**/*.server.ts - Presenters:
app/v3/presenters/**/*.server.ts
Route Convention
Routes use Remix flat-file convention with dot-separated segments:
api.v1.tasks.$taskId.trigger.ts -> /api/v1/tasks/:taskId/trigger
Environment Variables
Access via env export from app/env.server.ts. Never use process.env directly.
For testable code, never import env.server.ts in test files. Pass configuration as options instead:
realtimeClient.server.ts(testable service, takes config as constructor arg)realtimeClientGlobal.server.ts(creates singleton with env config)
Run Engine 2.0
The webapp integrates @internal/run-engine via app/v3/runEngine.server.ts. This is the singleton engine instance. Services in app/v3/services/ call engine methods for all run lifecycle operations (triggering, completing, cancelling, etc.).
The engineVersion.server.ts file determines V1 vs V2 for a given environment. New code should always target V2.
Background Workers
Background job workers use @trigger.dev/redis-worker:
app/v3/commonWorker.server.tsapp/v3/alertsWorker.server.tsapp/v3/batchTriggerWorker.server.ts
Do NOT add new jobs using zodworker/graphile-worker (legacy).
Real-time
- Socket.io:
app/v3/handleSocketIo.server.ts,app/v3/handleWebsockets.server.ts - Electric SQL: Powers real-time data sync for the dashboard
Legacy V1 Code
The app/v3/ directory name is misleading - most code is actively used by V2. Only these specific files are V1-only legacy:
app/v3/marqs/(old MarQS queue system)app/v3/legacyRunEngineWorker.server.tsapp/v3/services/triggerTaskV1.server.tsapp/v3/services/cancelTaskRunV1.server.tsapp/v3/authenticatedSocketConnection.server.tsapp/v3/sharedSocketConnection.ts
Some services (e.g., cancelTaskRun.server.ts, batchTriggerV3.server.ts) branch on RunEngineVersion to support both V1 and V2. When editing these, only modify V2 code paths.
Performance: Trigger Hot Path
The triggerTask.server.ts service is the highest-throughput code path in the system. Every API trigger call goes through it. Keep it fast:
- Do NOT add database queries to
triggerTask.server.tsorbatchTriggerV3.server.ts. Task defaults (TTL, etc.) are resolved viabackgroundWorkerTask.findFirst()in the queue concern (queues.server.ts) - one query per request, in mutually exclusive branches depending on locked/non-locked path. Piggyback on the existing query instead of adding new ones. - Two-stage resolution pattern: Task metadata is resolved in two stages by design:
- Trigger time (
triggerTask.server.ts): Only TTL is resolved from task defaults. Everything else uses whatever the caller provides. - Dequeue time (
dequeueSystem.ts): FullBackgroundWorkerTaskis loaded and retry config, machine config, maxDuration, etc. are resolved against task defaults.
- Trigger time (
- If you need to add a new task-level default, add it to the existing
selectclause in thebackgroundWorkerTask.findFirst()query — do NOT add a second query. If the default doesn't need to be known at trigger time, resolve it at dequeue time instead. - Batch triggers (
batchTriggerV3.server.ts) follow the same pattern — keep batch paths equally fast.
Prisma Query Patterns
- Always use
findFirstinstead offindUnique. Prisma'sfindUniquehas an implicit DataLoader that batches concurrent calls into a singleINquery. This batching cannot be disabled and has active bugs even in Prisma 6.x: uppercase UUIDs returning null (#25484, confirmed 6.4.1), composite key SQL correctness issues (#22202), and 5-10x worse performance than manual DataLoader (#6573, open since 2021).findFirstis never batched and avoids this entire class of issues.
React Patterns
- Only use
useCallback/useMemofor context provider values, expensive derived data that is a dependency elsewhere, or stable refs required by a dependency array. Don't wrap ordinary event handlers or trivial computations. - Use named constants for sentinel/placeholder values (e.g.
const UNSET_VALUE = "__unset__") instead of raw string literals scattered across comparisons.