chore(webapp): Concurrency page UI improvements (#2825)

### UI Improvements to the Concurrency page: 

- Truncates long branch names and includes a tooltip
- The Tables have a new variant if you don't want the rows to highlight
on hover
- Small fix to pluralize some words in the purchase modal
- Fix to prevent tooltip buttons being `type=submit`
- Updates the /limits docs page to include purchasing more concurrency
- Adds a clear banner when you have a positive balance of unallocated
concurrency



https://github.com/user-attachments/assets/54d927c3-84e3-4d55-8f42-726098f4daf0
This commit is contained in:
James Ritchie
2026-01-07 14:13:36 +00:00
committed by GitHub
parent 17338050b1
commit 583d299891
6 changed files with 64 additions and 15 deletions
@@ -51,10 +51,14 @@ export function EnvironmentCombo({
environment,
className,
iconClassName,
tooltipSideOffset,
tooltipSide,
}: {
environment: Environment;
className?: string;
iconClassName?: string;
tooltipSideOffset?: number;
tooltipSide?: "top" | "right" | "bottom" | "left";
}) {
return (
<span className={cn("flex items-center gap-1.5 text-sm text-text-bright", className)}>
@@ -62,7 +66,11 @@ export function EnvironmentCombo({
environment={environment}
className={cn("size-4.5 shrink-0", iconClassName)}
/>
<EnvironmentLabel environment={environment} />
<EnvironmentLabel
environment={environment}
tooltipSideOffset={tooltipSideOffset}
tooltipSide={tooltipSide}
/>
</span>
);
}
@@ -70,9 +78,13 @@ export function EnvironmentCombo({
export function EnvironmentLabel({
environment,
className,
tooltipSideOffset = 34,
tooltipSide = "right",
}: {
environment: Environment;
className?: string;
tooltipSideOffset?: number;
tooltipSide?: "top" | "right" | "bottom" | "left";
}) {
const spanRef = useRef<HTMLSpanElement>(null);
const [isTruncated, setIsTruncated] = useState(false);
@@ -115,9 +127,10 @@ export function EnvironmentLabel({
{text}
</span>
}
side="right"
side={tooltipSide}
variant="dark"
sideOffset={34}
sideOffset={tooltipSideOffset}
disableHoverableContent
/>
);
}
@@ -389,7 +389,7 @@ export const LinkButton = ({
to={to}
ref={innerRef}
replace={replace}
className={cn("group/button block focus-custom", props.fullWidth ? "w-full" : "")}
className={cn("group/button block focus-custom", props.fullWidth ? "w-full" : "w-fit")}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseEnter={onMouseEnter}
@@ -15,6 +15,14 @@ const variants = {
menuButtonDivider: "group-hover/table-row:border-charcoal-600/70",
rowSelected: "bg-charcoal-750 group-hover/table-row:bg-charcoal-750",
},
"bright/no-hover": {
header: "bg-transparent",
cell: "group-hover/table-row:bg-transparent",
stickyCell: "bg-background-bright",
menuButton: "bg-background-bright",
menuButtonDivider: "",
rowSelected: "bg-charcoal-750",
},
dimmed: {
header: "bg-background-dimmed",
cell: "group-hover/table-row:bg-charcoal-800 group-has-[[tabindex='0']:focus]/table-row:bg-background-bright",
@@ -85,6 +85,7 @@ function SimpleTooltip({
<TooltipProvider disableHoverableContent={disableHoverableContent}>
<Tooltip open={open} onOpenChange={onOpenChange}>
<TooltipTrigger
type="button"
tabIndex={-1}
className={cn("h-fit", buttonClassName)}
style={buttonStyle}
@@ -1,6 +1,7 @@
import { conform, useFieldList, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import {
ArrowDownIcon,
EnvelopeIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
@@ -18,6 +19,7 @@ import {
import { json, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { tryCatch } from "@trigger.dev/core";
import { useEffect, useState } from "react";
import simplur from "simplur";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
@@ -331,7 +333,7 @@ function Upgradable({
disabled={unallocated < 0 ? false : allocationModified}
/>
</div>
<Table>
<Table variant="bright/no-hover">
<TableBody>
<TableRow>
<TableCell className="pl-0 text-text-bright">Extra concurrency purchased</TableCell>
@@ -379,8 +381,15 @@ function Upgradable({
)}
</TableCell>
</TableRow>
<TableRow className={allocationModified ? undefined : "after:bg-transparent"}>
<TableCell colSpan={2} className="py-0">
<TableRow
className={
allocationModified || unallocated > 0 ? undefined : "after:bg-transparent"
}
>
<TableCell
colSpan={2}
className={cn("py-0", (unallocated > 0 || allocationModified) && "pr-0")}
>
<div className="flex h-10 items-center">
{allocationModified ? (
unallocated < 0 ? (
@@ -419,6 +428,16 @@ function Upgradable({
</Button>
</div>
)
) : unallocated > 0 ? (
<div className="flex h-full w-full items-center justify-between bg-success/10 px-2.5">
<div className="flex items-center justify-start gap-1">
<InformationCircleIcon className="size-4 text-success" />
<span className="text-success">
You have {unallocated} extra concurrency available to allocate below.
</span>
</div>
<ArrowDownIcon className="size-4 animate-bounce text-success" />
</div>
) : (
<></>
)}
@@ -434,7 +453,7 @@ function Upgradable({
<div className="flex items-center pb-1">
<Header3 className="grow">Concurrency allocation</Header3>
</div>
<Table>
<Table variant="bright/no-hover">
<TableHeader>
<TableRow>
<TableHeaderCell className="pl-0">Environment</TableHeaderCell>
@@ -452,7 +471,12 @@ function Upgradable({
{environments.map((environment, index) => (
<TableRow key={environment.id}>
<TableCell>
<EnvironmentCombo environment={environment} />
<EnvironmentCombo
environment={environment}
className="max-w-[18ch]"
tooltipSideOffset={6}
tooltipSide="top"
/>
</TableCell>
<TableCell alignment="right">{environment.planConcurrencyLimit}</TableCell>
<TableCell alignment="right">
@@ -523,7 +547,7 @@ function NotUpgradable({ environments }: { environments: EnvironmentWithConcurre
</>
) : null}
<div className="mt-3 flex flex-col gap-3">
<Table>
<Table variant="bright/no-hover">
<TableHeader>
<TableRow>
<TableHeaderCell className="pl-0">Environment</TableHeaderCell>
@@ -682,7 +706,7 @@ function PurchaseConcurrencyModal({
</div>
<div className="grid grid-cols-2 text-xs">
<span className="text-text-dimmed">
({extraConcurrency / concurrencyPricing.stepSize} bundles)
({simplur`${extraConcurrency / concurrencyPricing.stepSize} bundle[|s]`})
</span>
<span className="justify-self-end text-text-dimmed">/mth</span>
</div>
@@ -703,8 +727,11 @@ function PurchaseConcurrencyModal({
</div>
<div className="grid grid-cols-2 text-xs">
<span className="text-text-dimmed">
({(amountValue - extraConcurrency) / concurrencyPricing.stepSize} bundles @{" "}
{formatCurrency(concurrencyPricing.centsPerStep / 100, true)}/mth)
(
{simplur`${
(amountValue - extraConcurrency) / concurrencyPricing.stepSize
} bundle[|s]`}{" "}
@ {formatCurrency(concurrencyPricing.centsPerStep / 100, true)}/mth)
</span>
<span className="justify-self-end text-text-dimmed">/mth</span>
</div>
@@ -723,7 +750,7 @@ function PurchaseConcurrencyModal({
</div>
<div className="grid grid-cols-2 text-xs">
<span className="text-text-dimmed">
({amountValue / concurrencyPricing.stepSize} bundles)
({simplur`${amountValue / concurrencyPricing.stepSize} bundle[|s]`})
</span>
<span className="justify-self-end text-text-dimmed">/mth</span>
</div>
+1 -1
View File
@@ -13,7 +13,7 @@ import RateLimitHitUseBatchTrigger from "/snippets/rate-limit-hit-use-batchtrigg
| Hobby | 25 concurrent runs |
| Pro | 100+ concurrent runs |
Additional bundles above the Pro tier are available for $50/month per 50 concurrent runs. Contact us via [email](https://trigger.dev/contact) or [Discord](https://trigger.dev/discord) to request more.
Extra concurrency above the Pro tier limit is available via the dashboard. Click the "Concurrency" page from the left sidebar when on the Pro plan to purchase more.
## Rate limits