fix(webapp): restore admin debug tooltip on Tasks and Runs, make its IDs copyable (#4332)

Restores the debug panel on the **Tasks** and **Runs** pages, and makes
the data it shows copyable.

Admin/impersonation only — no change for regular users, so there's no
`.server-changes`

<img width="909" height="1420" alt="CleanShot 2026-07-22 at 12 05 27@2x"
src="https://github.com/user-attachments/assets/ce2da167-dc23-422f-83f3-4f4aee9ed32c"
/>
This commit is contained in:
James Ritchie
2026-07-22 13:10:31 +01:00
committed by GitHub
parent 81eac67069
commit 11d8a05fa6
14 changed files with 118 additions and 51 deletions
@@ -1,4 +1,5 @@
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
import { CopyableText } from "~/components/primitives/CopyableText";
import * as Property from "~/components/primitives/PropertyTable";
import {
Tooltip,
@@ -25,7 +26,10 @@ export function AdminDebugTooltip({ children }: { children?: React.ReactNode })
<TooltipTrigger>
<ShieldCheckIcon className="size-5" />
</TooltipTrigger>
<TooltipContent className="max-h-[90vh] overflow-y-auto">
{/* The copy controls below pass `hideTooltip` so their own tooltips don't fire
Radix's global close and dismiss this panel. `pr-8` leaves room for the
copy button, which is absolutely positioned to the right of each value. */}
<TooltipContent className="max-h-[90vh] overflow-y-auto pr-8">
<Content>{children}</Content>
</TooltipContent>
</Tooltip>
@@ -44,23 +48,31 @@ function Content({ children }: { children: React.ReactNode }) {
<Property.Table>
<Property.Item>
<Property.Label>User ID</Property.Label>
<Property.Value>{user.id}</Property.Value>
<Property.Value>
<CopyableText value={user.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
{organization && (
<Property.Item>
<Property.Label>Org ID</Property.Label>
<Property.Value>{organization.id}</Property.Value>
<Property.Value>
<CopyableText value={organization.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
)}
{project && (
<>
<Property.Item>
<Property.Label>Project ID</Property.Label>
<Property.Value>{project.id}</Property.Value>
<Property.Value>
<CopyableText value={project.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Project ref</Property.Label>
<Property.Value>{project.externalRef}</Property.Value>
<Property.Value>
<CopyableText value={project.externalRef} asChild hideTooltip />
</Property.Value>
</Property.Item>
</>
)}
@@ -68,7 +80,9 @@ function Content({ children }: { children: React.ReactNode }) {
<>
<Property.Item>
<Property.Label>Environment ID</Property.Label>
<Property.Value>{environment.id}</Property.Value>
<Property.Value>
<CopyableText value={environment.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Environment type</Property.Label>
@@ -81,7 +95,7 @@ function Content({ children }: { children: React.ReactNode }) {
</>
)}
</Property.Table>
<div className="pt-2">{children}</div>
{children && <div className="pt-2">{children}</div>}
</div>
);
}
@@ -11,12 +11,19 @@ export function CopyableText({
className,
asChild,
variant,
hideTooltip,
}: {
value: string;
copyValue?: string;
className?: string;
asChild?: boolean;
variant?: "icon-right" | "text-below";
/**
* Hide the "Copy"/"Copied" hint tooltip. Use when this is rendered inside another
* Radix tooltip (e.g. the admin debug panel): the nested tooltip would otherwise
* fire Radix's global "one tooltip open at a time" close and dismiss the parent.
*/
hideTooltip?: boolean;
}) {
const [isHovered, setIsHovered] = useState(false);
const { copy, copied } = useCopy(copyValue ?? value);
@@ -24,6 +31,24 @@ export function CopyableText({
const resolvedVariant = variant ?? "icon-right";
if (resolvedVariant === "icon-right") {
const iconButton = (
<span
className={cn(
"ml-1 flex size-6 items-center justify-center rounded border border-border-bright bg-background-hover",
asChild && "p-1",
copied
? "text-green-500"
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
)}
>
{copied ? (
<ClipboardCheckIcon className="size-3.5" />
) : (
<ClipboardIcon className="size-3.5" />
)}
</span>
);
return (
<span
className={cn("group relative inline-flex h-6 items-center", className)}
@@ -38,29 +63,17 @@ export function CopyableText({
isHovered ? "flex" : "hidden"
)}
>
<SimpleTooltip
button={
<span
className={cn(
"ml-1 flex size-6 items-center justify-center rounded border border-border-bright bg-background-hover",
asChild && "p-1",
copied
? "text-green-500"
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
)}
>
{copied ? (
<ClipboardCheckIcon className="size-3.5" />
) : (
<ClipboardIcon className="size-3.5" />
)}
</span>
}
content={copied ? "Copied!" : "Copy"}
className="font-sans"
disableHoverableContent
asChild={asChild}
/>
{hideTooltip ? (
iconButton
) : (
<SimpleTooltip
button={iconButton}
content={copied ? "Copied!" : "Copy"}
className="font-sans"
disableHoverableContent
asChild={asChild}
/>
)}
</span>
</span>
);
@@ -16,6 +16,7 @@ import { PlusIcon } from "~/assets/icons/PlusIcon";
import { QuestionMarkIcon } from "~/assets/icons/QuestionMarkIcon";
import { RunsIcon } from "~/assets/icons/RunsIcon";
import { TaskIcon } from "~/assets/icons/TaskIcon";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CodeBlock } from "~/components/code/CodeBlock";
import { InlineCode } from "~/components/code/InlineCode";
import { HasNoTasksDeployed, HasNoTasksDev } from "~/components/BlankStatePanels";
@@ -261,6 +262,7 @@ export default function Page() {
<NavBar>
<PageTitle title="Tasks" accessory={<TasksHelpTooltip />} />
<PageAccessories>
<AdminDebugTooltip />
<LinkButton
variant={"docs/small"}
LeadingIcon={BookOpenIcon}
@@ -2,6 +2,7 @@ import { BookOpenIcon } from "@heroicons/react/20/solid";
import { type MetaFunction } from "@remix-run/react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CopyableText } from "~/components/primitives/CopyableText";
import { CodeBlock } from "~/components/code/CodeBlock";
import { InlineCode } from "~/components/code/InlineCode";
import {
@@ -113,7 +114,9 @@ export default function Page() {
<Property.Table>
<Property.Item key={environment.id}>
<Property.Label>{environment.slug}</Property.Label>
<Property.Value>{environment.id}</Property.Value>
<Property.Value>
<CopyableText value={environment.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
</Property.Table>
</AdminDebugTooltip>
@@ -242,7 +242,9 @@ export default function Page() {
{branches.map((branch) => (
<Property.Item key={branch.id}>
<Property.Label>{branch.branchName}</Property.Label>
<Property.Value>{branch.id}</Property.Value>
<Property.Value>
<CopyableText value={branch.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
))}
</Property.Table>
@@ -21,6 +21,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
import simplur from "simplur";
import { z } from "zod";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CopyableText } from "~/components/primitives/CopyableText";
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
import { Feedback } from "~/components/Feedback";
import {
@@ -270,7 +271,9 @@ export default function Page() {
{environment.type}{" "}
{environment.branchName ? ` (${environment.branchName})` : ""}
</Property.Label>
<Property.Value>{environment.id}</Property.Value>
<Property.Value>
<CopyableText value={environment.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
))}
</Property.Table>
@@ -18,6 +18,7 @@ import { GitMetadata } from "~/components/GitMetadata";
import { VercelLink } from "~/components/integrations/VercelLink";
import { RuntimeIcon } from "~/components/RuntimeIcon";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CopyableText } from "~/components/primitives/CopyableText";
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
import { Badge } from "~/components/primitives/Badge";
import { LinkButton } from "~/components/primitives/Buttons";
@@ -283,20 +284,28 @@ export default function Page() {
<Property.Table>
<Property.Item>
<Property.Label>ID</Property.Label>
<Property.Value>{deployment.id}</Property.Value>
<Property.Value>
<CopyableText value={deployment.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Project ID</Property.Label>
<Property.Value>{deployment.projectId}</Property.Value>
<Property.Value>
<CopyableText value={deployment.projectId} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Org ID</Property.Label>
<Property.Value>{deployment.organizationId}</Property.Value>
<Property.Value>
<CopyableText value={deployment.organizationId} asChild hideTooltip />
</Property.Value>
</Property.Item>
{deployment.imageReference && (
<Property.Item>
<Property.Label>Image</Property.Label>
<Property.Value>{deployment.imageReference}</Property.Value>
<Property.Value>
<CopyableText value={deployment.imageReference} asChild hideTooltip />
</Property.Value>
</Property.Item>
)}
<Property.Item>
@@ -95,7 +95,9 @@ export default function Page() {
{branches.map((branch) => (
<Property.Item key={branch.id}>
<Property.Label>{branch.branchName}</Property.Label>
<Property.Value>{branch.id}</Property.Value>
<Property.Value>
<CopyableText value={branch.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
))}
</Property.Table>
@@ -7,6 +7,7 @@ import { Gauge } from "lucide-react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { ConcurrencyIcon } from "~/assets/icons/ConcurrencyIcon";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CopyableText } from "~/components/primitives/CopyableText";
import { Feedback } from "~/components/Feedback";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { EnvironmentSelector } from "~/components/navigation/EnvironmentSelector";
@@ -125,7 +126,9 @@ export default function Page() {
</Property.Item>
<Property.Item>
<Property.Label>Organization ID</Property.Label>
<Property.Value>{data.organizationId}</Property.Value>
<Property.Value>
<CopyableText value={data.organizationId} asChild hideTooltip />
</Property.Value>
</Property.Item>
</Property.Table>
</AdminDebugTooltip>
@@ -163,7 +163,9 @@ export default function Page() {
{regions.map((region) => (
<Property.Item key={region.id}>
<Property.Label>{region.name}</Property.Label>
<Property.Value>{region.id}</Property.Value>
<Property.Value>
<CopyableText value={region.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
))}
</Property.Table>
@@ -489,19 +489,27 @@ export default function Page() {
<Property.Table>
<Property.Item>
<Property.Label>ID</Property.Label>
<Property.Value>{run.id}</Property.Value>
<Property.Value>
<CopyableText value={run.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Trace ID</Property.Label>
<Property.Value>{run.traceId}</Property.Value>
<Property.Value>
<CopyableText value={run.traceId} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Env ID</Property.Label>
<Property.Value>{run.environment.id}</Property.Value>
<Property.Value>
<CopyableText value={run.environment.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Org ID</Property.Label>
<Property.Value>{run.environment.organizationId}</Property.Value>
<Property.Value>
<CopyableText value={run.environment.organizationId} asChild hideTooltip />
</Property.Value>
</Property.Item>
</Property.Table>
</AdminDebugTooltip>
@@ -11,6 +11,7 @@ import {
import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon";
import { QuestionMarkIcon } from "~/assets/icons/QuestionMarkIcon";
import { TaskIcon } from "~/assets/icons/TaskIcon";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { DevDisconnectedBanner, useDevPresence } from "~/components/DevPresence";
import { InlineCode } from "~/components/code/InlineCode";
import { StepContentContainer } from "~/components/StepContentContainer";
@@ -166,6 +167,7 @@ export default function Page() {
<DevDisconnectedBanner isConnected={isConnected} />
)}
<PageAccessories>
<AdminDebugTooltip />
<LinkButton
variant={"docs/small"}
LeadingIcon={BookOpenIcon}
@@ -2,9 +2,9 @@ import { Outlet, type MetaFunction } from "@remix-run/react";
import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import * as Property from "~/components/primitives/PropertyTable";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CopyableText } from "~/components/primitives/CopyableText";
import { useProject } from "~/hooks/useProject";
import { requireUserId } from "~/services/session.server";
import {
@@ -55,14 +55,15 @@ export default function SettingsLayout() {
<Property.Table>
<Property.Item>
<Property.Label>ID</Property.Label>
<Property.Value>{project.id}</Property.Value>
<div className="flex items-center gap-2">
<Paragraph variant="extra-small/bright/mono">{project.id}</Paragraph>
</div>
<Property.Value>
<CopyableText value={project.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Org ID</Property.Label>
<Property.Value>{project.organizationId}</Property.Value>
<Property.Value>
<CopyableText value={project.organizationId} asChild hideTooltip />
</Property.Value>
</Property.Item>
</Property.Table>
</AdminDebugTooltip>
@@ -18,6 +18,7 @@ import { z } from "zod";
import { Feedback } from "~/components/Feedback";
import { UserAvatar } from "~/components/UserProfilePhoto";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CopyableText } from "~/components/primitives/CopyableText";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import {
Alert,
@@ -372,7 +373,9 @@ export default function Page() {
<Property.Table>
<Property.Item>
<Property.Label>Org ID</Property.Label>
<Property.Value>{organization.id}</Property.Value>
<Property.Value>
<CopyableText value={organization.id} asChild hideTooltip />
</Property.Value>
</Property.Item>
{members.map((member) => (