From 9e06c111fd57dca04f7850c2dffda4115311e902 Mon Sep 17 00:00:00 2001 From: Oskar Otwinowski Date: Thu, 11 Dec 2025 11:54:39 +0000 Subject: [PATCH 1/4] fix(webapp): Fix for adjacent tasks --- .../route.tsx | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 5253e56f2e..3616bffeb6 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -23,7 +23,7 @@ import { } from "@trigger.dev/core/v3"; import type { RuntimeEnvironmentType } from "@trigger.dev/database"; import { motion } from "framer-motion"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { redirect } from "remix-typedjson"; import { MoveToTopIcon } from "~/assets/icons/MoveToTopIcon"; @@ -320,7 +320,7 @@ export default function Page() { text: "Runs", }} title={<> - + {tableState && (
@@ -1674,55 +1674,53 @@ function useAdjacentRunPaths({ run: { friendlyId: string }; runsList: RunsListNavigation | null; }): [string | null, string | null] { - return useMemo(() => { - if (!runsList || runsList.runs.length === 0) { - return [null, null]; - } + if (!runsList || runsList.runs.length === 0) { + return [null, null]; + } - const currentIndex = runsList.runs.findIndex((r) => r.friendlyId === run.friendlyId); - - if (currentIndex === -1) { - return [null, null]; - } + const currentIndex = runsList.runs.findIndex((r) => r.friendlyId === run.friendlyId); + + if (currentIndex === -1) { + return [null, null]; + } - // Determine previous run: use prevPageLastRun if at first position, otherwise use previous run in list - let previousRun: { friendlyId: string } | null = null; - const previousRunTableState = new URLSearchParams(tableState); - if (currentIndex > 0) { - previousRun = runsList.runs[currentIndex - 1]; - } else if (runsList.prevPageLastRun) { - previousRun = runsList.prevPageLastRun; - // Update tableState with the new cursor for the previous page - previousRunTableState.set("cursor", runsList.prevPageLastRun.cursor); - previousRunTableState.set("direction", "backward"); - } + // Determine previous run: use prevPageLastRun if at first position, otherwise use previous run in list + let previousRun: { friendlyId: string } | null = null; + const previousRunTableState = new URLSearchParams(tableState); + if (currentIndex > 0) { + previousRun = runsList.runs[currentIndex - 1]; + } else if (runsList.prevPageLastRun) { + previousRun = runsList.prevPageLastRun; + // Update tableState with the new cursor for the previous page + previousRunTableState.set("cursor", runsList.prevPageLastRun.cursor); + previousRunTableState.set("direction", "backward"); + } - // Determine next run: use nextPageFirstRun if at last position, otherwise use next run in list - let nextRun: { friendlyId: string } | null = null; - const nextRunTableState = new URLSearchParams(tableState); - if (currentIndex < runsList.runs.length - 1) { - nextRun = runsList.runs[currentIndex + 1]; - } else if (runsList.nextPageFirstRun) { - nextRun = runsList.nextPageFirstRun; - // Update tableState with the new cursor for the next page - nextRunTableState.set("cursor", runsList.nextPageFirstRun.cursor); - nextRunTableState.set("direction", "forward"); - } + // Determine next run: use nextPageFirstRun if at last position, otherwise use next run in list + let nextRun: { friendlyId: string } | null = null; + const nextRunTableState = new URLSearchParams(tableState); + if (currentIndex < runsList.runs.length - 1) { + nextRun = runsList.runs[currentIndex + 1]; + } else if (runsList.nextPageFirstRun) { + nextRun = runsList.nextPageFirstRun; + // Update tableState with the new cursor for the next page + nextRunTableState.set("cursor", runsList.nextPageFirstRun.cursor); + nextRunTableState.set("direction", "forward"); + } - const previousURLSearchParams = new URLSearchParams(); - previousURLSearchParams.set("tableState", previousRunTableState.toString()); - const previousRunPath = previousRun - ? v3RunPath(organization, project, environment, previousRun, previousURLSearchParams) - : null; + const previousURLSearchParams = new URLSearchParams(); + previousURLSearchParams.set("tableState", previousRunTableState.toString()); + const previousRunPath = previousRun + ? v3RunPath(organization, project, environment, previousRun, previousURLSearchParams) + : null; - const nextURLSearchParams = new URLSearchParams(); - nextURLSearchParams.set("tableState", nextRunTableState.toString()); - const nextRunPath = nextRun - ? v3RunPath(organization, project, environment, nextRun, nextURLSearchParams) - : null; + const nextURLSearchParams = new URLSearchParams(); + nextURLSearchParams.set("tableState", nextRunTableState.toString()); + const nextRunPath = nextRun + ? v3RunPath(organization, project, environment, nextRun, nextURLSearchParams) + : null; - return [previousRunPath, nextRunPath]; - }, [organization, project, environment, tableState, run.friendlyId, runsList]); + return [previousRunPath, nextRunPath]; } From bf2cd23e2d8879b39a357689f6fb64b28b1c6013 Mon Sep 17 00:00:00 2001 From: Oskar Otwinowski Date: Thu, 11 Dec 2025 14:38:46 +0000 Subject: [PATCH 2/4] feat(webapp): Disable animations for finished spans in Runs table, improve UI/UX of adjacent Runs feature --- .../route.tsx | 59 +++++++++++++------ .../route.tsx | 17 +++++- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 3616bffeb6..00c475e53a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -140,10 +140,10 @@ const resizableSettings = { type TraceEvent = NonNullable["trace"]>["events"][0]; type RunsListNavigation = { - runs: Array<{ friendlyId: string }>; + runs: Array<{ friendlyId: string; spanId: string }>; pagination: { next?: string; previous?: string }; - prevPageLastRun?: { friendlyId: string; cursor: string }; - nextPageFirstRun?: { friendlyId: string; cursor: string }; + prevPageLastRun?: { friendlyId: string; spanId: string; cursor: string }; + nextPageFirstRun?: { friendlyId: string; spanId: string; cursor: string }; }; async function getRunsListFromTableState({ @@ -204,6 +204,7 @@ async function getRunsListFromTableState({ if (prevPageResult.runs.length > 0) { runsList.prevPageLastRun = { friendlyId: prevPageResult.runs[0].friendlyId, + spanId: prevPageResult.runs[0].spanId, cursor: currentPageResult.pagination.previous, }; } @@ -222,6 +223,7 @@ async function getRunsListFromTableState({ if (nextPageResult.runs.length > 0) { runsList.nextPageFirstRun = { friendlyId: nextPageResult.runs[0].friendlyId, + spanId: nextPageResult.runs[0].spanId, cursor: currentPageResult.pagination.next, }; } @@ -296,7 +298,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { type LoaderData = SerializeFrom; export default function Page() { - const { run, trace, resizable, maximumLiveReloadingSetting, runsList } = useLoaderData(); + const { run, trace, maximumLiveReloadingSetting, runsList } = useLoaderData(); const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); @@ -308,8 +310,10 @@ export default function Page() { const tableState = decodeURIComponent(value("tableState") ?? ""); const tableStateSearchParams = new URLSearchParams(tableState); const filters = getRunFiltersFromSearchParams(tableStateSearchParams); + const tabParam = value("tab") ?? undefined; + const spanParam = value("span") ?? undefined; - const [previousRunPath, nextRunPath] = useAdjacentRunPaths({organization, project, environment, tableState, run, runsList}); + const [previousRunPath, nextRunPath] = useAdjacentRunPaths({organization, project, environment, tableState, run, runsList, tabParam, useSpan: !!spanParam}); return ( <> @@ -940,7 +944,6 @@ function TimelineView({ scale, rootSpanStatus, rootStartedAt, - parentRef, timelineScrollRef, virtualizer, events, @@ -1127,7 +1130,8 @@ function TimelineView({ "-ml-[0.5px] h-[0.5625rem] w-px rounded-none", eventBackgroundClassName(node.data) )} - layoutId={`${node.id}-${event.name}`} + layoutId={node.data.isPartial ? `${node.id}-${event.name}` : undefined} + animate={!node.data.isPartial ? false : undefined} /> )} @@ -1145,7 +1149,8 @@ function TimelineView({ "-ml-[0.1562rem] size-[0.3125rem] rounded-full border bg-background-bright", eventBorderClassName(node.data) )} - layoutId={`${node.id}-${event.name}`} + layoutId={node.data.isPartial ? `${node.id}-${event.name}` : undefined} + animate={!node.data.isPartial ? false : undefined} /> )} @@ -1164,7 +1169,8 @@ function TimelineView({ > ) : null} @@ -1201,7 +1207,8 @@ function TimelineView({ "-ml-0.5 size-3 rounded-full border-2 border-background-bright", eventBackgroundClassName(node.data) )} - layoutId={node.id} + layoutId={node.data.isPartial ? node.id : undefined} + animate={!node.data.isPartial ? false : undefined} /> )} @@ -1444,7 +1451,8 @@ function SpanWithDuration({ fadeLeft ? "rounded-r-sm bg-gradient-to-r from-black/50 to-transparent" : "rounded-sm" )} style={{ backgroundSize: "20px 100%", backgroundRepeat: "no-repeat" }} - layoutId={node.id} + layoutId={node.data.isPartial ? node.id : undefined} + animate={!node.data.isPartial ? false : undefined} > {node.data.isPartial && (
{formatDurationMilliseconds(props.durationMs, { style: "short", @@ -1543,12 +1553,11 @@ function KeyboardShortcuts({ expandAllBelowDepth, collapseAllBelowDepth, toggleExpandLevel, - setShowDurations, }: { expandAllBelowDepth: (depth: number) => void; collapseAllBelowDepth: (depth: number) => void; toggleExpandLevel: (depth: number) => void; - setShowDurations: (show: (show: boolean) => boolean) => void; + setShowDurations?: (show: (show: boolean) => boolean) => void; }) { return ( <> @@ -1666,13 +1675,17 @@ function useAdjacentRunPaths({ tableState, run, runsList, + tabParam, + useSpan }: { organization: { slug: string }; project: { slug: string }; environment: { slug: string }; tableState: string; - run: { friendlyId: string }; + run: { friendlyId: string, spanId: string }; runsList: RunsListNavigation | null; + tabParam?: string; + useSpan?: boolean; }): [string | null, string | null] { if (!runsList || runsList.runs.length === 0) { return [null, null]; @@ -1685,7 +1698,7 @@ function useAdjacentRunPaths({ } // Determine previous run: use prevPageLastRun if at first position, otherwise use previous run in list - let previousRun: { friendlyId: string } | null = null; + let previousRun: { friendlyId: string; spanId: string } | null = null; const previousRunTableState = new URLSearchParams(tableState); if (currentIndex > 0) { previousRun = runsList.runs[currentIndex - 1]; @@ -1697,7 +1710,7 @@ function useAdjacentRunPaths({ } // Determine next run: use nextPageFirstRun if at last position, otherwise use next run in list - let nextRun: { friendlyId: string } | null = null; + let nextRun: { friendlyId: string; spanId: string } | null = null; const nextRunTableState = new URLSearchParams(tableState); if (currentIndex < runsList.runs.length - 1) { nextRun = runsList.runs[currentIndex + 1]; @@ -1710,12 +1723,24 @@ function useAdjacentRunPaths({ const previousURLSearchParams = new URLSearchParams(); previousURLSearchParams.set("tableState", previousRunTableState.toString()); + if (previousRun && useSpan) { + previousURLSearchParams.set("span", previousRun.spanId); + } + if (tabParam && useSpan) { + previousURLSearchParams.set("tab", tabParam); + } const previousRunPath = previousRun ? v3RunPath(organization, project, environment, previousRun, previousURLSearchParams) : null; const nextURLSearchParams = new URLSearchParams(); nextURLSearchParams.set("tableState", nextRunTableState.toString()); + if (nextRun && useSpan) { + nextURLSearchParams.set("span", nextRun.spanId); + } + if (tabParam && useSpan) { + nextURLSearchParams.set("tab", tabParam); + } const nextRunPath = nextRun ? v3RunPath(organization, project, environment, nextRun, nextURLSearchParams) : null; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx index 8c41f0ceac..8f80124707 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx @@ -55,6 +55,7 @@ import { v3CreateBulkActionPath, v3ProjectPath, v3TestPath, + v3TestTaskPath, } from "~/utils/pathBuilder"; import { ListPagination } from "../../components/ListPagination"; import { CreateBulkActionInspector } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction"; @@ -235,7 +236,13 @@ function RunsList({ list.possibleTasks.length === 0 ? ( ) : ( - + t.slug === list.filters.tasks[0]) + : undefined + } + /> ) ) : (
@@ -339,7 +346,7 @@ function CreateFirstTaskInstructions() { ); } -function RunTaskInstructions() { +function RunTaskInstructions({ task }: { task?: { slug: string } }) { const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); @@ -352,7 +359,11 @@ function RunTaskInstructions() { Perform a test run with a payload directly from the dashboard. Date: Thu, 11 Dec 2025 15:15:19 +0000 Subject: [PATCH 3/4] feat(webapp): Replace history when moving to adjacent runs --- apps/webapp/app/components/primitives/Buttons.tsx | 4 +++- .../route.tsx | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/components/primitives/Buttons.tsx b/apps/webapp/app/components/primitives/Buttons.tsx index 67ba3c0924..c2845f1040 100644 --- a/apps/webapp/app/components/primitives/Buttons.tsx +++ b/apps/webapp/app/components/primitives/Buttons.tsx @@ -331,7 +331,7 @@ export const Button = forwardRef( type LinkPropsType = Pick< LinkProps, "to" | "target" | "onClick" | "onMouseDown" | "onMouseEnter" | "onMouseLeave" | "download" -> & { disabled?: boolean } & React.ComponentProps; +> & { disabled?: boolean; replace?: boolean } & React.ComponentProps; export const LinkButton = ({ to, onClick, @@ -340,6 +340,7 @@ export const LinkButton = ({ onMouseLeave, download, disabled = false, + replace, ...props }: LinkPropsType) => { const innerRef = useRef(null); @@ -387,6 +388,7 @@ export const LinkButton = ({
); @@ -1784,6 +1785,7 @@ function NextRunButton({ to }: { to: string | null }) { shortcut={{ key: "]" }} tooltip="Next Run" disabled={!to} + replace />
); From ed86e156c1150931612b672a0b666fc0206c1093 Mon Sep 17 00:00:00 2001 From: Oskar Otwinowski Date: Thu, 11 Dec 2025 16:59:58 +0000 Subject: [PATCH 4/4] chore(pr-gh-action): Increase max heap mem size for typecheck --- .github/workflows/typecheck.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index d7722a46f0..3eb98e5177 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -35,6 +35,8 @@ jobs: - name: 🔎 Type check run: pnpm run typecheck + env: + NODE_OPTIONS: --max-old-space-size=8192 - name: 🔎 Check exports run: pnpm run check-exports