Preserve issue breadcrumb source

This commit is contained in:
Dotta
2026-03-10 20:59:55 -05:00
parent 4b49efa02e
commit b5935349ed
2 changed files with 27 additions and 5 deletions

View File

@@ -11,6 +11,7 @@ import { heartbeatsApi } from "../api/heartbeats";
import { useCompany } from "../context/CompanyContext"; import { useCompany } from "../context/CompanyContext";
import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys"; import { queryKeys } from "../lib/queryKeys";
import { createIssueDetailLocationState } from "../lib/issueDetailBreadcrumb";
import { StatusIcon } from "../components/StatusIcon"; import { StatusIcon } from "../components/StatusIcon";
import { PriorityIcon } from "../components/PriorityIcon"; import { PriorityIcon } from "../components/PriorityIcon";
import { EmptyState } from "../components/EmptyState"; import { EmptyState } from "../components/EmptyState";
@@ -171,11 +172,13 @@ function FailedRunCard({
run, run,
issueById, issueById,
agentName: linkedAgentName, agentName: linkedAgentName,
issueLinkState,
onDismiss, onDismiss,
}: { }: {
run: HeartbeatRun; run: HeartbeatRun;
issueById: Map<string, Issue>; issueById: Map<string, Issue>;
agentName: string | null; agentName: string | null;
issueLinkState: unknown;
onDismiss: () => void; onDismiss: () => void;
}) { }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -227,6 +230,7 @@ function FailedRunCard({
{issue ? ( {issue ? (
<Link <Link
to={`/issues/${issue.identifier ?? issue.id}`} to={`/issues/${issue.identifier ?? issue.id}`}
state={issueLinkState}
className="block truncate text-sm font-medium transition-colors hover:text-foreground no-underline text-inherit" className="block truncate text-sm font-medium transition-colors hover:text-foreground no-underline text-inherit"
> >
<span className="font-mono text-muted-foreground mr-1.5"> <span className="font-mono text-muted-foreground mr-1.5">
@@ -315,6 +319,14 @@ export function Inbox() {
const pathSegment = location.pathname.split("/").pop() ?? "new"; const pathSegment = location.pathname.split("/").pop() ?? "new";
const tab: InboxTab = pathSegment === "all" ? "all" : "new"; const tab: InboxTab = pathSegment === "all" ? "all" : "new";
const issueLinkState = useMemo(
() =>
createIssueDetailLocationState(
"Inbox",
`${location.pathname}${location.search}${location.hash}`,
),
[location.pathname, location.search, location.hash],
);
const { data: agents } = useQuery({ const { data: agents } = useQuery({
queryKey: queryKeys.agents.list(selectedCompanyId!), queryKey: queryKeys.agents.list(selectedCompanyId!),
@@ -749,6 +761,7 @@ export function Inbox() {
run={run} run={run}
issueById={issueById} issueById={issueById}
agentName={agentName(run.agentId)} agentName={agentName(run.agentId)}
issueLinkState={issueLinkState}
onDismiss={() => dismiss(`run:${run.id}`)} onDismiss={() => dismiss(`run:${run.id}`)}
/> />
))} ))}
@@ -890,6 +903,7 @@ export function Inbox() {
<Link <Link
key={issue.id} key={issue.id}
to={`/issues/${issue.identifier ?? issue.id}`} to={`/issues/${issue.identifier ?? issue.id}`}
state={issueLinkState}
className="flex min-w-0 cursor-pointer items-start gap-2 px-3 py-3 no-underline text-inherit transition-colors hover:bg-accent/50 sm:items-center sm:gap-3 sm:px-4" className="flex min-w-0 cursor-pointer items-start gap-2 px-3 py-3 no-underline text-inherit transition-colors hover:bg-accent/50 sm:items-center sm:gap-3 sm:px-4"
> >
{/* Status icon - left column on mobile, inline on desktop */} {/* Status icon - left column on mobile, inline on desktop */}

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react"; import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
import { useParams, Link, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate, useParams } from "@/lib/router";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { issuesApi } from "../api/issues"; import { issuesApi } from "../api/issues";
import { activityApi } from "../api/activity"; import { activityApi } from "../api/activity";
@@ -11,6 +11,7 @@ import { useCompany } from "../context/CompanyContext";
import { usePanel } from "../context/PanelContext"; import { usePanel } from "../context/PanelContext";
import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys"; import { queryKeys } from "../lib/queryKeys";
import { readIssueDetailBreadcrumb } from "../lib/issueDetailBreadcrumb";
import { useProjectOrder } from "../hooks/useProjectOrder"; import { useProjectOrder } from "../hooks/useProjectOrder";
import { relativeTime, cn, formatTokens } from "../lib/utils"; import { relativeTime, cn, formatTokens } from "../lib/utils";
import { InlineEditor } from "../components/InlineEditor"; import { InlineEditor } from "../components/InlineEditor";
@@ -150,6 +151,7 @@ export function IssueDetail() {
const { setBreadcrumbs } = useBreadcrumbs(); const { setBreadcrumbs } = useBreadcrumbs();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const [moreOpen, setMoreOpen] = useState(false); const [moreOpen, setMoreOpen] = useState(false);
const [mobilePropsOpen, setMobilePropsOpen] = useState(false); const [mobilePropsOpen, setMobilePropsOpen] = useState(false);
const [detailTab, setDetailTab] = useState("comments"); const [detailTab, setDetailTab] = useState("comments");
@@ -213,6 +215,10 @@ export function IssueDetail() {
}); });
const hasLiveRuns = (liveRuns ?? []).length > 0 || !!activeRun; const hasLiveRuns = (liveRuns ?? []).length > 0 || !!activeRun;
const sourceBreadcrumb = useMemo(
() => readIssueDetailBreadcrumb(location.state) ?? { label: "Issues", href: "/issues" },
[location.state],
);
// Filter out runs already shown by the live widget to avoid duplication // Filter out runs already shown by the live widget to avoid duplication
const timelineRuns = useMemo(() => { const timelineRuns = useMemo(() => {
@@ -468,17 +474,17 @@ export function IssueDetail() {
useEffect(() => { useEffect(() => {
const titleLabel = issue?.title ?? issueId ?? "Issue"; const titleLabel = issue?.title ?? issueId ?? "Issue";
setBreadcrumbs([ setBreadcrumbs([
{ label: "Issues", href: "/issues" }, sourceBreadcrumb,
{ label: hasLiveRuns ? `🔵 ${titleLabel}` : titleLabel }, { label: hasLiveRuns ? `🔵 ${titleLabel}` : titleLabel },
]); ]);
}, [setBreadcrumbs, issue, issueId, hasLiveRuns]); }, [setBreadcrumbs, sourceBreadcrumb, issue, issueId, hasLiveRuns]);
// Redirect to identifier-based URL if navigated via UUID // Redirect to identifier-based URL if navigated via UUID
useEffect(() => { useEffect(() => {
if (issue?.identifier && issueId !== issue.identifier) { if (issue?.identifier && issueId !== issue.identifier) {
navigate(`/issues/${issue.identifier}`, { replace: true }); navigate(`/issues/${issue.identifier}`, { replace: true, state: location.state });
} }
}, [issue, issueId, navigate]); }, [issue, issueId, navigate, location.state]);
useEffect(() => { useEffect(() => {
if (!issue?.id) return; if (!issue?.id) return;
@@ -524,6 +530,7 @@ export function IssueDetail() {
{i > 0 && <ChevronRight className="h-3 w-3 shrink-0" />} {i > 0 && <ChevronRight className="h-3 w-3 shrink-0" />}
<Link <Link
to={`/issues/${ancestor.identifier ?? ancestor.id}`} to={`/issues/${ancestor.identifier ?? ancestor.id}`}
state={location.state}
className="hover:text-foreground transition-colors truncate max-w-[200px]" className="hover:text-foreground transition-colors truncate max-w-[200px]"
title={ancestor.title} title={ancestor.title}
> >
@@ -800,6 +807,7 @@ export function IssueDetail() {
<Link <Link
key={child.id} key={child.id}
to={`/issues/${child.identifier ?? child.id}`} to={`/issues/${child.identifier ?? child.id}`}
state={location.state}
className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent/20 transition-colors" className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent/20 transition-colors"
> >
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">