Queue metrics and health UI#4289
Conversation
…tenant drilldown Queues list: header charts (env saturation, backlog, delay p95, throttled) with synced hover and drag-to-zoom into the time filter, live Queued/Running blocks polled from ClickHouse, sortable columns, per-queue pause/override controls, and clearer readouts (duration/percent axes, share-of-window throttled, hover explanations). Queue detail: live Concurrency/Queued/Oldest-wait blocks (CH-polled, with window peak/worst captions), pause/override/view-runs actions on the blocks, four square trend charts, a Concurrency keys tab with per-key charts, a sortable keys table, key search, per-key drilldown, and the live most-starved key named on the worst-wait card. Shared: small-icon button size, chart sync isolation for maximized charts, tooltip flicker/positioning fixes, queues-purple chart palette, hidden-tab polling pause, 30-day period cap, and a synthetic metrics seeder for the design environment.
Re-seeding overwrote the materialized concurrencyLimit while leaving a manually-set override percent behind, so the table showed e.g. "10 (77%)" against an env limit of 25 while the override dialog correctly computed 19.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThe pull request adds percent-based queue concurrency overrides with persistence, validation, recalculation, API and UI controls, and integration tests. Queue metrics gain throttling aggregates, live polling, refreshed charts, sorting, and synchronized interactions. Shared chart and metrics-layout components are introduced and applied to queue, agent, usage, and Storybook pages. Queue allocation data becomes aggregate-only, while queue detail pages gain searchable concurrency-key views and live statistics. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Add a presentational compound layout (MetricsLayout.Root/.Filters/.Grid/ .Content) for metric/dashboard pages: one page-level scroll, named slots, and a count-adaptive tile grid. Showcase it in a route-based storybook page demonstrating the auto-adapting grids and a tabs-vs-table content slot.
…ages Rebuild both queues pages on the shared MetricsLayout skeleton (Filters / Grid / Content slots + single page scroll). No visual change: same spacing, grids and breakpoints; the stat grids now derive their columns from the tile count, chart grids pass explicit columns.
Extend the presentational MetricsLayout compound with two optional capabilities so pages with a persistent side panel and independently scrolling regions can adopt it: - MetricsLayout.Sidebar renders a persistent panel beside the main column. Fixed-width by default (width prop); an optional resizable mode uses the shared Resizable primitives with autosaveId/snapshot cookie persistence. - Root gains a scroll prop: "page" (default, single page-level scroll, byte-identical to before) or "regions" (Root only bounds height so the page composes its own scrolling areas). No behavior change for existing pages, which pass neither prop.
Add a demo selector to the MetricsLayout storybook with fixed and resizable sidebar examples plus a scroll="regions" example showing two independently scrolling areas. Existing overview demo is preserved.
Hoist the Filters slot out of the scroll container (the dashboards' auto/1fr pattern) so filters stay visible; the detail page carries the inset on the filter row itself.
Unify the agent detail page with the shared metrics-page skeleton so it reads like every other metrics page (queues list/detail): a filter row on top, a visualization tile row, then tabs + table content, plus a persistent resizable sidebar. - The TimeFilter and pagination move UP into MetricsLayout.Filters, unfused from the tabs (they previously shared one h-10 bar with the tabs). - The Sessions/LLM spend/Tokens charts become a fixed-height MetricsLayout.Grid tile row (three-up), keeping chart sync + drag-to-zoom. - The tabs now sit alone on their row in MetricsLayout.Content, with the table flowing below them in the single page-level scroll. - The agent config panel becomes MetricsLayout.Sidebar (resizable), now wired to autosaveId "agent-detail-sidebar" + a getResizableSnapshot loader snapshot so the split persists across reloads. Intentional behavior change: the old vertical drag-split between the charts row and the table is DROPPED. The unified skeleton has no such split — the charts get a fixed-height row and the table flows in the page scroll. No data or loader logic changed.
…in the sort button A header cell that is both sortable and has a tooltip rendered the InfoIconTooltip trigger (a <button>) inside the sort <button>, producing invalid DOM (validateDOMNesting button-in-button warnings). Keep the label + sort indicator inside the sort button and render the tooltip trigger as a sibling in a shared flex row, preserving placement and the always-visible tooltip icon.
… recalc recalculatePercentLimits skipped the whole queue when the materialized limit already matched the DB value, so if the DB write succeeded but the engine push had failed on a prior run, the DB and engine stayed diverged forever. Now the DB write is skipped only when unchanged, but the (idempotent) engine push always runs for non-paused queues, so a previously-failed sync converges on the next recalc. Also documents why the paused-env recalc call in AllocateConcurrencyService is intentional (queue pushes on a paused env are inert and avoid reconciliation on resume). Adds a test proving the retry semantics.
…tail presenter The queue detail presenter did not carry the percent source-of-truth, so the detail page's override dialog reopened percent overrides in absolute mode. Surface concurrencyLimitOverridePercent as a sibling field on the returned queue, mirroring QueueListPresenter.
Remove freeform className (and pageBodyClassName) from every MetricsLayout slot so pages can no longer restyle the layout's spacing, borders or gutters. Slots now own their chrome and bake one visual rhythm: - Filters: pinned 40px bar with a bottom border and standard insets; pages compose left/right clusters as child divs (justify-between). - Root: owns the scroll and the content column (flex-col, gap-3, vertical padding); no page-specific padding knob. - Grid: baked px-3 gutter + gap-3; adds kind="tiles"|"charts" where "charts" bakes the fixed chart-row height (shrink-0 so it holds in the flex column). - Content: full-bleed by default (edge-to-edge table); inset for a padded column; always bakes a doubled (24px) separation above it. - Sidebar: keeps its semantic props, drops className. Update both queues routes and the storybook to the hardened API, and rewrite the JSDoc to state the philosophy (slots own the chrome; new needs become new semantic variants, not classes).
…l page Follow-up to the MetricsLayout hardening (closed semantic props, no className on slots): the Filters bar drops its className in favour of the baked pinned chrome with left/right cluster divs, and the charts grid becomes kind="charts" (baked fixed row height) instead of a classNamed grid inside a sized wrapper.
The env Queued/Running tiles, Environment-limit tile and pause/increase controls no longer disappear when the allocation summary is unavailable. Only the Allocated tile depends on it, and it now shows a placeholder so the four-tile grid keeps its shape.
- Reject mutations on archived branches, matching the queues list route. - The live "Oldest wait" now ignores concurrency keys whose backlog has drained, so a stale key can't over-report the wait. - The concurrency override dialog reopens in percent mode for percent overrides instead of always defaulting to an absolute value.
The panel library's min/default/max props are template-literal Unit types; bare strings failed typecheck (would have failed CI where the billing baseline noise is absent).
|
Preview Deployment
|
…e percent bounds Reuse the exported ACTIVITY_CHART_PEAK_CLASS on the env index blank state, and extract MIN/MAX_QUEUE_OVERRIDE_PERCENT + isValidQueueOverridePercent so the API schema, the dashboard mutation handler, and the override service validate the same bound.
…a chart The shared tooltip element dropped indicator="line", so stacked areas fell back to a dot. Add a stable module-level StackedAreaTooltip so the stacked view keeps its line indicator without reintroducing the portaled-tooltip remount flicker.
…alc self-heal test
…og header Every data cell in the queues metrics table now links to the queue detail page (mirroring the runs table's full-row navigation), while the pause/resume + override controls, row action menu, and in-cell tooltips punch through without navigating. Also right-aligns the Backlog column header.
Both Overview and Concurrency keys tabs now always render on the queue detail page. When a queue has no concurrency keys, the keys tab shows a standard empty-state panel with a docs link instead of blank charts. The per-key stats table now spans the full width edge-to-edge (matching the Queues list table) while the per-key charts and the key drill-down keep their padded column.
…ization % Charts are the queues-purple now, so the tooltips no longer say 'blue'. In the Concurrency block, a separator keeps the limit and the % from reading as one number (e.g. '/ 25' + '44%'). Also drop a stale server-changes note.
Gauges are only emitted while a queue is active, so a drained queue's newest bucket holds its last non-zero reading. Trust the CH gauge only when its newest bucket is recent; past that, fall back to the loader's Redis/PG value instead of lingering on a stale count.
Full-row navigation wrapped each cell's content in a <Link>, but the queue-type icon, override/at-limit tooltips, and the 'Override' limited-by trigger are real <button>s — invalid <a><button> nesting that fails a11y audits. Add leading/ trailing slots to TableCell so those triggers render beside the link, not inside it, and drop 'to' from the all-interactive Limited by cell. The row still navigates and the controls still work.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/webapp/app/components/primitives/Table.tsx (1)
433-443: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSupport
leadingContentandtrailingContentforonClickand non-interactive cells.The JSDoc for
leadingContentandtrailingContentexplicitly states they are meant for a "to/onClickcell", but the implementation currently ignores these props ifonClickis used withoutto, or if the cell is non-interactive.Ensure the adornments render as expected by mirroring the wrapping pattern used in the
tobranch.💻 Proposed fix
) : onClick ? ( - <button - onClick={onClick} - className={cn("cursor-pointer focus:outline-hidden", flexClasses, actionClassName)} - tabIndex={isTabbableCell ? 0 : -1} - > - {children} - </button> + leadingContent || trailingContent ? ( + <div className={flexClasses}> + {leadingContent} + <button + onClick={onClick} + className={cn( + "inline-flex items-center gap-2 cursor-pointer focus:outline-hidden", + actionClassName + )} + tabIndex={isTabbableCell ? 0 : -1} + > + {children} + </button> + {trailingContent} + </div> + ) : ( + <button + onClick={onClick} + className={cn("cursor-pointer focus:outline-hidden", flexClasses, actionClassName)} + tabIndex={isTabbableCell ? 0 : -1} + > + {children} + </button> + ) ) : ( - <>{children}</> + leadingContent || trailingContent ? ( + <div className={flexClasses}> + {leadingContent} + {children} + {trailingContent} + </div> + ) : ( + <>{children}</> + ) )}
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 2fb5fd86-ff9d-4f69-8cc4-67e5af8082ba
📒 Files selected for processing (3)
apps/webapp/app/components/primitives/Table.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues_.$queueParam/route.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/webapp/app/routes/app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.$queueParam/route.tsx
- apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
**/*.{ts,tsx}: Prefer static imports over dynamicimport(); use dynamic imports only for unresolvable circular dependencies, genuine performance code splitting, or conditional runtime loading.
Import Trigger.dev tasks from@trigger.dev/sdk; never use@trigger.dev/sdk/v3or deprecatedclient.defineJob.
Add agentcrumbs while writing code using approved namespaces; mark lines with//@Crumbsor blocks with `// `#region` `@crumbs, and strip them before merging.
Files:
apps/webapp/app/components/primitives/Table.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use zod for validation in packages/core and apps/webapp
Files:
apps/webapp/app/components/primitives/Table.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
apps/webapp/app/components/primitives/Table.tsx
apps/webapp/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
apps/webapp/**/*.{ts,tsx}: Access environment variables through theenvexport ofenv.server.tsinstead of directly accessingprocess.env
Use subpath exports from@trigger.dev/corepackage instead of importing from the root@trigger.dev/corepathDo not reintroduce the removed v1 execution path;
RunEngineVersion.V1branches may only reject or finalize gracefully so v3 clients receive a clean 4xx, never a 5xx.
Files:
apps/webapp/app/components/primitives/Table.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
For apps, use
typecheckfor verification and never usebuildas the correctness check.
Files:
apps/webapp/app/components/primitives/Table.tsx
apps/webapp/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
apps/webapp/app/**/*.{ts,tsx}: For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.
UseuseCallbackanduseMemoonly for context provider values, expensive derived data used as a dependency, or stable references required by dependency arrays; do not wrap ordinary event handlers or trivial computations.
Use named constants for sentinel or placeholder values instead of scattering raw string literals across comparisons.
Files:
apps/webapp/app/components/primitives/Table.tsx
🧠 Learnings (15)
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-06-13T19:53:13.759Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3937
File: packages/trigger-sdk/skills/realtime-and-frontend/SKILL.md:258-260
Timestamp: 2026-06-13T19:53:13.759Z
Learning: When reviewing code that uses `trigger.dev/react-hooks`’s `useRealtimeRun`, preserve the call signature where the first argument is the full realtime handle object (not `handle.id`). This is intentional to maintain type-safety and is consistent with the official docs; do not suggest changing the first argument from the handle object to `handle.id`.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-06-17T17:13:49.929Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3948
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx:48-62
Timestamp: 2026-06-17T17:13:49.929Z
Learning: In triggerdotdev/trigger.dev, within `dashboardLoader`/`dashboardAction` (or similar context resolver code) whenever you resolve an organization ID from an organization slug for RBAC/enterprise authorization scope, always read from the primary Prisma client (`prisma`), not `$replica`. Using `$replica` can hit replica-lag and cause the RBAC lookup/authorization to run without the correct org scope (bypassing intended role enforcement). Implement the slug→org lookup with `prisma.organization.findFirst(...)` (or equivalent primary-client query) and add an inline comment documenting why the primary client is required (replica lag could lead to unscoped RBAC checks).
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-06-23T13:04:21.413Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4023
File: apps/webapp/app/services/upsertBranch.server.ts:14-18
Timestamp: 2026-06-23T13:04:21.413Z
Learning: In TypeScript, it’s valid to `import { type X }` and then use `typeof X` in a type-only position, e.g. `type Alias = z.infer<typeof X>`. The `type` modifier suppresses the runtime import, but the type checker still has the full exported type so `z.infer<typeof X>` can resolve correctly. In code reviews, don’t flag this as a TypeScript compile error as long as `typeof X` is used in a type context (e.g., with `z.infer`, `type` aliases, generics), not as a runtime value.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-04-16T14:21:15.229Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3368
File: apps/webapp/app/components/logs/LogsTaskFilter.tsx:135-163
Timestamp: 2026-04-16T14:21:15.229Z
Learning: When rendering lists of task registry items in apps/webapp (e.g., <SelectItem /> rows) and using `key={item.slug}`, do not flag it as potentially non-unique. In trigger.dev’s `TaskIdentifier` table, the DB constraint `@unique([runtimeEnvironmentId, slug])` guarantees `slug` is unique within a given runtime environment, so `item.slug` is safe as the React key as long as the list is derived from that registry/constraint (and not from a legacy query that could produce duplicate slugs).
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-05-08T21:00:20.973Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3538
File: apps/webapp/app/components/primitives/Resizable.tsx:60-78
Timestamp: 2026-05-08T21:00:20.973Z
Learning: In the triggerdotdev/trigger.dev codebase, treat Zod as a boundary validation tool (API handlers, request/response validation, and storage/DB read/write validation), not as inline render-time validation inside React components/primitive UI code. For render-time guards, prefer small manual type-narrowing checks (e.g., a short predicate like ~10–20 lines) over importing Zod into UI primitives, to avoid per-render schema-parse overhead and unnecessary abstraction. Use the manual guard approach unless you truly need schema validation at a boundary; only then introduce Zod.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-06-25T18:21:55.847Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4039
File: apps/webapp/app/routes/invite-resend.tsx:0-0
Timestamp: 2026-06-25T18:21:55.847Z
Learning: In the triggerdotdev/trigger.dev Zod 4 migration, avoid importing from the root package `conform-to/zod` in webapp code. It can resolve to the Zod 3 build and may crash at module load under Zod 4. When reviewing TypeScript/TSX files in `apps/webapp`, prefer importing from the Zod 4 subpath `conform-to/zod/v4` for Zod 4-compatible schemas/types.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-05-12T21:04:05.815Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3542
File: apps/webapp/app/components/sessions/v1/SessionStatus.tsx:1-3
Timestamp: 2026-05-12T21:04:05.815Z
Learning: In this Remix + TypeScript codebase, do not flag a server/client boundary violation when a file imports only types from a module matching `*.server`.
Specifically, it’s safe to import types using `import type { Foo } from "*.server"` or `import { type Foo } from "*.server"` because TypeScript erases type-only imports at compile time and they emit no JavaScript, so they won’t cross the Remix server/client bundle boundary.
Only raise the boundary concern for value imports (e.g., `import { Foo }` without `type`, or `import Foo`), since those produce JavaScript output.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-06-25T18:21:51.905Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4039
File: apps/webapp/app/routes/invite-revoke.tsx:0-0
Timestamp: 2026-06-25T18:21:51.905Z
Learning: During the Zod v4 migration in the triggerdotdev/trigger.dev webapp, ensure any imports from `conform-to/zod` use the Zod-4 subpath: `conform-to/zod/v4` (e.g., `import { parseWithZod } from "conform-to/zod/v4"`). Do not import from the package root `conform-to/zod`, because it is the Zod 3 implementation and may load Zod-3-only symbols (e.g., `ZodBranded`, `ZodEffects`), which can throw at module load (notably with `zod4.4.3`). This should be enforced across `apps/webapp/**/*` where helpers like `parseWithZod` and `conformZodMessage` are used.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-07-03T17:10:21.498Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 4148
File: apps/webapp/app/models/orgMember.server.ts:149-168
Timestamp: 2026-07-03T17:10:21.498Z
Learning: In triggerdotdev/trigger.dev, `User.email` (Prisma schema: `internal-packages/database/prisma/schema.prisma`) currently does NOT use `citext` and does NOT have a `lower(email)` functional unique index. Therefore, do not introduce Prisma queries like `where: { email: { equals: <value>, mode: "insensitive" } }` (or any case-insensitive lookup) against `User.email`, because it can force sequential scans of the `users` table under load. During review, ensure email is normalized (e.g., lowercased/trimmed) before both writes and subsequent lookups, and if true case-insensitive behavior/uniqueness is required, implement it via a separate app-wide migration (e.g., switch to `citext` and/or add a functional unique index with backfill) rather than bolting it onto individual feature PRs.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
📚 Learning: 2026-06-25T18:21:54.729Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4039
File: apps/webapp/app/routes/confirm-basic-details.tsx:0-0
Timestamp: 2026-06-25T18:21:54.729Z
Learning: For Remix + TypeScript files that use Conform v1 (conform-to/react) and its getInputProps helper, when you intend to suppress the helper-provided default value for non-checkbox/non-radio inputs (e.g., hidden inputs managed via an explicit value prop), use the Conform v1 option key `value: false`. Do not recommend `defaultValue: false` here, because `defaultValue` is not a valid option key for these input types in Conform v1 typings.
Applied to files:
apps/webapp/app/components/primitives/Table.tsx
The Concurrency chart's tooltip promised 'yellow when at the limit' but nothing was recolored. Add an at-or-above warningOverlay variant (running >= limit) and wire it up, so the line turns warning where the queue is saturated.
Queue pages design overhaul (frontend for the queue-metrics feature)
Builds the dashboards on top of the queue-metrics pipeline (base branch).
Queues list
Queue detail
Under the hood
MetricsLayoutthat is currently used for/queues + $id,/agents + $id,/settings/usage