Preserve issue breadcrumb source
This commit is contained in:
@@ -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 */}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user