Skip to content

Commit db0df17

Browse files
authored
chore(webapp): Run navigation UI improvements (#2802)
**Improvements to the run ID copy button and run navigation buttons for consistency** - Adds some x-padding and layout adjustment to the copy ID button. <img width="664" height="114" alt="CleanShot 2025-12-19 at 16 05 21@2x" src="https://github.com/user-attachments/assets/ebc8e0de-011b-419c-bdcc-eb4157553d1c" /> - New custom navigation icons that work better at tiny sizes <img width="330" height="196" alt="CleanShot 2025-12-19 at 16 06 38@2x" src="https://github.com/user-attachments/assets/bfd8d6b8-8a65-4eac-9ce1-d70acf0ad265" /> Some other small improvements/fixes: - Fixes a browser html error where there was a <button> inside a <button> - Updates the shortcut description to match the tooltip text for consistency - Made the hover states more consistent - The shortcut bar at the bottom snaps to the list sooner because there are more items now
1 parent edf5b14 commit db0df17

File tree

5 files changed

+112
-56
lines changed

5 files changed

+112
-56
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function ChevronExtraSmallDown({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M15 6L9.75926 12.1142C9.36016 12.5798 8.63984 12.5798 8.24074 12.1142L3 6"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeMiterlimit="1.00244"
9+
strokeLinecap="round"
10+
/>
11+
</svg>
12+
);
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function ChevronExtraSmallUp({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M3 12L8.24074 5.8858C8.63984 5.42019 9.36016 5.42019 9.75926 5.8858L15 12"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeMiterlimit="1.00244"
9+
strokeLinecap="round"
10+
/>
11+
</svg>
12+
);
13+
}

apps/webapp/app/components/Shortcuts.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function ShortcutContent() {
134134
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium/bright" />
135135
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium/bright" />
136136
</Shortcut>
137-
<Shortcut name="Jump to adjacent">
137+
<Shortcut name="Jump to next/previous run">
138138
<ShortcutKey shortcut={{ key: "[" }} variant="medium/bright" />
139139
<ShortcutKey shortcut={{ key: "]" }} variant="medium/bright" />
140140
</Shortcut>

apps/webapp/app/components/primitives/CopyableText.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,24 @@ export function CopyableText({
7272
button={
7373
<Button
7474
variant="minimal/small"
75-
onClick={(e) => {
75+
onClick={(e) => {
7676
e.stopPropagation();
77-
copy();
77+
copy();
7878
}}
7979
className={cn(
80-
"cursor-pointer bg-transparent py-0 px-1 text-left text-text-bright transition-colors hover:text-white hover:bg-transparent",
80+
"cursor-pointer bg-transparent px-1 py-0 text-left text-text-dimmed transition-colors hover:bg-transparent",
8181
className
8282
)}
8383
>
84-
<span>{value}</span>
84+
<span className="transition-colors group-hover/button:text-text-bright">{value}</span>
8585
</Button>
8686
}
87-
content={copied ? "Copied" : "Click to copy"}
88-
className="font-sans px-2 py-1"
87+
content={copied ? "Copied" : "Copy"}
88+
className="px-2 py-1 font-sans"
8989
disableHoverableContent
9090
open={isHovered || copied}
9191
onOpenChange={setIsHovered}
92+
asChild
9293
/>
9394
);
9495
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
ArrowUturnLeftIcon,
33
BoltSlashIcon,
44
BookOpenIcon,
5-
ChevronUpIcon,
65
ChevronDownIcon,
76
ChevronRightIcon,
87
InformationCircleIcon,
@@ -12,6 +11,7 @@ import {
1211
MagnifyingGlassPlusIcon,
1312
StopCircleIcon,
1413
} from "@heroicons/react/20/solid";
14+
1515
import { useLoaderData, useRevalidator } from "@remix-run/react";
1616
import { type LoaderFunctionArgs, type SerializeFrom, json } from "@remix-run/server-runtime";
1717
import { type Virtualizer } from "@tanstack/react-virtual";
@@ -26,6 +26,8 @@ import { motion } from "framer-motion";
2626
import { useCallback, useEffect, useRef, useState } from "react";
2727
import { useHotkeys } from "react-hotkeys-hook";
2828
import { redirect } from "remix-typedjson";
29+
import { ChevronExtraSmallDown } from "~/assets/icons/ChevronExtraSmallDown";
30+
import { ChevronExtraSmallUp } from "~/assets/icons/ChevronExtraSmallUp";
2931
import { MoveToTopIcon } from "~/assets/icons/MoveToTopIcon";
3032
import { MoveUpIcon } from "~/assets/icons/MoveUpIcon";
3133
import tileBgPath from "~/assets/images/[email protected]";
@@ -35,6 +37,7 @@ import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
3537
import { PageBody } from "~/components/layout/AppLayout";
3638
import { Badge } from "~/components/primitives/Badge";
3739
import { Button, LinkButton } from "~/components/primitives/Buttons";
40+
import { CopyableText } from "~/components/primitives/CopyableText";
3841
import { DateTimeShort } from "~/components/primitives/DateTime";
3942
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
4043
import { Header3 } from "~/components/primitives/Headers";
@@ -62,24 +65,32 @@ import {
6265
import { type NodesState } from "~/components/primitives/TreeView/reducer";
6366
import { CancelRunDialog } from "~/components/runs/v3/CancelRunDialog";
6467
import { ReplayRunDialog } from "~/components/runs/v3/ReplayRunDialog";
68+
import { getRunFiltersFromSearchParams } from "~/components/runs/v3/RunFilters";
6569
import { RunIcon } from "~/components/runs/v3/RunIcon";
6670
import {
6771
SpanTitle,
6872
eventBackgroundClassName,
6973
eventBorderClassName,
7074
} from "~/components/runs/v3/SpanTitle";
7175
import { TaskRunStatusIcon, runStatusClassNameColor } from "~/components/runs/v3/TaskRunStatus";
76+
import { $replica } from "~/db.server";
7277
import { useDebounce } from "~/hooks/useDebounce";
7378
import { useEnvironment } from "~/hooks/useEnvironment";
7479
import { useEventSource } from "~/hooks/useEventSource";
7580
import { useInitialDimensions } from "~/hooks/useInitialDimensions";
7681
import { useOrganization } from "~/hooks/useOrganizations";
7782
import { useProject } from "~/hooks/useProject";
7883
import { useReplaceSearchParams } from "~/hooks/useReplaceSearchParams";
84+
import { useSearchParams } from "~/hooks/useSearchParam";
7985
import { type Shortcut, useShortcutKeys } from "~/hooks/useShortcutKeys";
8086
import { useHasAdminAccess } from "~/hooks/useUser";
87+
import { findProjectBySlug } from "~/models/project.server";
88+
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
89+
import { NextRunListPresenter } from "~/presenters/v3/NextRunListPresenter.server";
8190
import { RunEnvironmentMismatchError, RunPresenter } from "~/presenters/v3/RunPresenter.server";
91+
import { clickhouseClient } from "~/services/clickhouseInstance.server";
8292
import { getImpersonationId } from "~/services/impersonation.server";
93+
import { logger } from "~/services/logger.server";
8394
import { getResizableSnapshot } from "~/services/resizablePanel.server";
8495
import { requireUserId } from "~/services/session.server";
8596
import { cn } from "~/utils/cn";
@@ -94,18 +105,9 @@ import {
94105
v3RunStreamingPath,
95106
v3RunsPath,
96107
} from "~/utils/pathBuilder";
108+
import type { SpanOverride } from "~/v3/eventRepository/eventRepository.types";
97109
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
98110
import { SpanView } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route";
99-
import { useSearchParams } from "~/hooks/useSearchParam";
100-
import { CopyableText } from "~/components/primitives/CopyableText";
101-
import type { SpanOverride } from "~/v3/eventRepository/eventRepository.types";
102-
import { getRunFiltersFromSearchParams } from "~/components/runs/v3/RunFilters";
103-
import { NextRunListPresenter } from "~/presenters/v3/NextRunListPresenter.server";
104-
import { $replica } from "~/db.server";
105-
import { clickhouseClient } from "~/services/clickhouseInstance.server";
106-
import { findProjectBySlug } from "~/models/project.server";
107-
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
108-
import { logger } from "~/services/logger.server";
109111

110112
const resizableSettings = {
111113
parent: {
@@ -210,7 +212,10 @@ async function getRunsListFromTableState({
210212
}
211213
}
212214

213-
if (currentRunIndex === currentPageResult.runs.length - 1 && currentPageResult.pagination.next) {
215+
if (
216+
currentRunIndex === currentPageResult.runs.length - 1 &&
217+
currentPageResult.pagination.next
218+
) {
214219
const nextPageResult = await runsListPresenter.call(project.organizationId, environment.id, {
215220
userId,
216221
projectId: project.id,
@@ -313,7 +318,16 @@ export default function Page() {
313318
const tabParam = value("tab") ?? undefined;
314319
const spanParam = value("span") ?? undefined;
315320

316-
const [previousRunPath, nextRunPath] = useAdjacentRunPaths({organization, project, environment, tableState, run, runsList, tabParam, useSpan: !!spanParam});
321+
const [previousRunPath, nextRunPath] = useAdjacentRunPaths({
322+
organization,
323+
project,
324+
environment,
325+
tableState,
326+
run,
327+
runsList,
328+
tabParam,
329+
useSpan: !!spanParam,
330+
});
317331

318332
return (
319333
<>
@@ -323,13 +337,21 @@ export default function Page() {
323337
to: v3RunsPath(organization, project, environment, filters),
324338
text: "Runs",
325339
}}
326-
title={<>
327-
<CopyableText value={run.friendlyId} variant="text-below" className="font-mono px-0 py-0 pb-[2px]"/>
328-
{tableState && (<div className="flex">
329-
<PreviousRunButton to={previousRunPath} />
330-
<NextRunButton to={nextRunPath} />
331-
</div>)}
332-
</>}
340+
title={
341+
<div className="flex items-center gap-x-0">
342+
<CopyableText
343+
value={run.friendlyId}
344+
variant="text-below"
345+
className="-ml-[0.4375rem] h-6 px-1.5 font-mono text-xs hover:text-text-bright"
346+
/>
347+
{tableState && (
348+
<div className="flex">
349+
<PreviousRunButton to={previousRunPath} />
350+
<NextRunButton to={nextRunPath} />
351+
</div>
352+
)}
353+
</div>
354+
}
333355
/>
334356
{environment.type === "DEVELOPMENT" && <DevDisconnectedBanner isConnected={isConnected} />}
335357
<PageAccessories>
@@ -407,16 +429,18 @@ export default function Page() {
407429
maximumLiveReloadingSetting={maximumLiveReloadingSetting}
408430
/>
409431
) : (
410-
<NoLogsView
411-
run={run}
412-
/>
432+
<NoLogsView run={run} />
413433
)}
414434
</PageBody>
415435
</>
416436
);
417437
}
418438

419-
function TraceView({ run, trace, maximumLiveReloadingSetting }: Pick<LoaderData, "run" | "trace" | "maximumLiveReloadingSetting">) {
439+
function TraceView({
440+
run,
441+
trace,
442+
maximumLiveReloadingSetting,
443+
}: Pick<LoaderData, "run" | "trace" | "maximumLiveReloadingSetting">) {
420444
const organization = useOrganization();
421445
const project = useProject();
422446
const environment = useEnvironment();
@@ -875,15 +899,15 @@ function TasksTreeView({
875899
</ResizablePanelGroup>
876900
<div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-4">
877901
<div className="grow @container">
878-
<div className="hidden items-center gap-4 @[42rem]:flex">
902+
<div className="hidden items-center gap-4 @[48rem]:flex">
879903
<KeyboardShortcuts
880904
expandAllBelowDepth={expandAllBelowDepth}
881905
collapseAllBelowDepth={collapseAllBelowDepth}
882906
toggleExpandLevel={toggleExpandLevel}
883907
setShowDurations={setShowDurations}
884908
/>
885909
</div>
886-
<div className="@[42rem]:hidden">
910+
<div className="@[48rem]:hidden">
887911
<Popover>
888912
<PopoverArrowTrigger>Shortcuts</PopoverArrowTrigger>
889913
<PopoverContent
@@ -1131,7 +1155,9 @@ function TimelineView({
11311155
"-ml-[0.5px] h-[0.5625rem] w-px rounded-none",
11321156
eventBackgroundClassName(node.data)
11331157
)}
1134-
layoutId={disableSpansAnimations ? undefined : `${node.id}-${event.name}`}
1158+
layoutId={
1159+
disableSpansAnimations ? undefined : `${node.id}-${event.name}`
1160+
}
11351161
animate={disableSpansAnimations ? false : undefined}
11361162
/>
11371163
)}
@@ -1150,7 +1176,9 @@ function TimelineView({
11501176
"-ml-[0.1562rem] size-[0.3125rem] rounded-full border bg-background-bright",
11511177
eventBorderClassName(node.data)
11521178
)}
1153-
layoutId={disableSpansAnimations ? undefined : `${node.id}-${event.name}`}
1179+
layoutId={
1180+
disableSpansAnimations ? undefined : `${node.id}-${event.name}`
1181+
}
11541182
animate={disableSpansAnimations ? false : undefined}
11551183
/>
11561184
)}
@@ -1444,7 +1472,12 @@ function SpanWithDuration({
14441472
fadeLeft,
14451473
disableAnimations,
14461474
...props
1447-
}: Timeline.SpanProps & { node: TraceEvent; showDuration: boolean; fadeLeft: boolean; disableAnimations?: boolean }) {
1475+
}: Timeline.SpanProps & {
1476+
node: TraceEvent;
1477+
showDuration: boolean;
1478+
fadeLeft: boolean;
1479+
disableAnimations?: boolean;
1480+
}) {
14481481
return (
14491482
<Timeline.Span {...props}>
14501483
<motion.div
@@ -1583,13 +1616,15 @@ function KeyboardShortcuts({
15831616
}
15841617

15851618
function AdjacentRunsShortcuts() {
1586-
return (<div className="flex items-center gap-0.5">
1619+
return (
1620+
<div className="flex items-center gap-0.5">
15871621
<ShortcutKey shortcut={{ key: "[" }} variant="medium" className="ml-0 mr-0 px-1" />
15881622
<ShortcutKey shortcut={{ key: "]" }} variant="medium" className="ml-0 mr-0 px-1" />
15891623
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
1590-
Adjacent runs
1624+
Next/previous run
15911625
</Paragraph>
1592-
</div>);
1626+
</div>
1627+
);
15931628
}
15941629

15951630
function ArrowKeyShortcuts() {
@@ -1679,13 +1714,13 @@ function useAdjacentRunPaths({
16791714
run,
16801715
runsList,
16811716
tabParam,
1682-
useSpan
1717+
useSpan,
16831718
}: {
16841719
organization: { slug: string };
16851720
project: { slug: string };
16861721
environment: { slug: string };
16871722
tableState: string;
1688-
run: { friendlyId: string, spanId: string };
1723+
run: { friendlyId: string; spanId: string };
16891724
runsList: RunsListNavigation | null;
16901725
tabParam?: string;
16911726
useSpan?: boolean;
@@ -1695,7 +1730,7 @@ function useAdjacentRunPaths({
16951730
}
16961731

16971732
const currentIndex = runsList.runs.findIndex((r) => r.friendlyId === run.friendlyId);
1698-
1733+
16991734
if (currentIndex === -1) {
17001735
return [null, null];
17011736
}
@@ -1751,18 +1786,15 @@ function useAdjacentRunPaths({
17511786
return [previousRunPath, nextRunPath];
17521787
}
17531788

1754-
17551789
function PreviousRunButton({ to }: { to: string | null }) {
17561790
return (
17571791
<div className={cn("peer/prev order-1", !to && "pointer-events-none")}>
17581792
<LinkButton
1759-
to={to ? to : '#'}
1793+
to={to ? to : "#"}
17601794
variant={"minimal/small"}
1761-
LeadingIcon={ChevronUpIcon}
1762-
className={cn(
1763-
"flex items-center rounded-r-none border-r-0 pl-2 pr-[0.5625rem]",
1764-
!to && "cursor-not-allowed opacity-50"
1765-
)}
1795+
LeadingIcon={ChevronExtraSmallUp}
1796+
leadingIconClassName="size-3 group-hover/button:text-text-bright transition-colors"
1797+
className={cn("flex size-6 max-w-6 items-center", !to && "cursor-not-allowed opacity-50")}
17661798
onClick={(e) => !to && e.preventDefault()}
17671799
shortcut={{ key: "[" }}
17681800
tooltip="Previous Run"
@@ -1777,13 +1809,11 @@ function NextRunButton({ to }: { to: string | null }) {
17771809
return (
17781810
<div className={cn("peer/next order-3", !to && "pointer-events-none")}>
17791811
<LinkButton
1780-
to={to ? to : '#'}
1812+
to={to ? to : "#"}
17811813
variant={"minimal/small"}
1782-
TrailingIcon={ChevronDownIcon}
1783-
className={cn(
1784-
"flex items-center rounded-l-none border-l-0 pl-[0.5625rem] pr-2",
1785-
!to && "cursor-not-allowed opacity-50"
1786-
)}
1814+
LeadingIcon={ChevronExtraSmallDown}
1815+
leadingIconClassName="size-3 group-hover/button:text-text-bright transition-colors"
1816+
className={cn("flex size-6 max-w-6 items-center", !to && "cursor-not-allowed opacity-50")}
17871817
onClick={(e) => !to && e.preventDefault()}
17881818
shortcut={{ key: "]" }}
17891819
tooltip="Next Run"
@@ -1793,4 +1823,3 @@ function NextRunButton({ to }: { to: string | null }) {
17931823
</div>
17941824
);
17951825
}
1796-

0 commit comments

Comments
 (0)