diff --git a/src/components/layout/NotificationCenter.tsx b/src/components/layout/NotificationCenter.tsx index ed580de..3ef2c00 100644 --- a/src/components/layout/NotificationCenter.tsx +++ b/src/components/layout/NotificationCenter.tsx @@ -142,19 +142,19 @@ export default function NotificationCenter() { }, [filter, notifications]); async function handleOpenNotification(notification: OrchaiNotification) { - if (!notification.read) { - try { - await markNotificationRead(notification.id); - setNotifications((prev) => - prev.map((n) => (n.id === notification.id ? { ...n, read: true } : n)) - ); - } catch { - // ignore - } - } - setOpen(false); + if (!notification.read) { + setNotifications((prev) => + prev.map((n) => (n.id === notification.id ? { ...n, read: true } : n)) + ); + + // Do not block navigation on read acknowledgement. + void markNotificationRead(notification.id).catch(() => { + // ignore + }); + } + if (notification.ticket_id) { navigate(`/tickets/${notification.ticket_id}`); return; diff --git a/src/components/tickets/TicketDetail.tsx b/src/components/tickets/TicketDetail.tsx index bd5bef2..5a507db 100644 --- a/src/components/tickets/TicketDetail.tsx +++ b/src/components/tickets/TicketDetail.tsx @@ -58,8 +58,11 @@ export default function TicketDetail() { const [targetBranch, setTargetBranch] = useState(""); const [availableBranches, setAvailableBranches] = useState([]); const [branchInputMode, setBranchInputMode] = useState<"select" | "manual">("select"); + const [branchesLoadedForWorktreeId, setBranchesLoadedForWorktreeId] = useState(null); const [branchesLoading, setBranchesLoading] = useState(false); const [branchesError, setBranchesError] = useState(""); + const [diffLoading, setDiffLoading] = useState(false); + const [diffError, setDiffError] = useState(""); const [tab, setTab] = useState<"info" | "analyst" | "developer" | "diff">("info"); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); @@ -93,29 +96,24 @@ export default function TicketDetail() { async function loadData() { if (!ticketId) return; + setError(""); try { const result = await getTicketResult(ticketId); setTicket(result.ticket); setWorktree(result.worktree); - - if (result.ticket.developer_report) setTab("developer"); - else if (result.ticket.analyst_report) setTab("analyst"); + setTab("info"); + setDiff(null); + setDiffError(""); + setDiffLoading(false); + setAvailableBranches([]); + setBranchInputMode("select"); + setTargetBranch(""); + setBranchesError(""); + setBranchesLoading(false); + setBranchesLoadedForWorktreeId(null); if (result.worktree && result.worktree.status === "Active") { - await loadBranchOptions(result.worktree.id); - try { - const d = await getWorktreeDiff(result.worktree.id); - setDiff(d); - } catch { - setDiff(null); - } - } else { - setDiff(null); - setAvailableBranches([]); - setBranchInputMode("select"); - setTargetBranch(""); - setBranchesError(""); - setBranchesLoading(false); + return; } } catch (err) { setError(getErrorMessage(err)); @@ -123,9 +121,61 @@ export default function TicketDetail() { } useEffect(() => { - loadData(); + void loadData(); }, [ticketId]); + useEffect(() => { + if (tab !== "info") return; + if (!worktree || worktree.status !== "Active") return; + if (branchesLoading) return; + if (branchesLoadedForWorktreeId === worktree.id) return; + + let cancelled = false; + + void (async () => { + await loadBranchOptions(worktree.id); + if (!cancelled) { + setBranchesLoadedForWorktreeId(worktree.id); + } + })(); + + return () => { + cancelled = true; + }; + }, [tab, worktree?.id, worktree?.status, branchesLoading, branchesLoadedForWorktreeId]); + + useEffect(() => { + if (tab !== "diff") return; + if (!worktree || worktree.status !== "Active") return; + if (diff !== null || diffLoading) return; + + let cancelled = false; + setDiffLoading(true); + setDiffError(""); + + void getWorktreeDiff(worktree.id) + .then((value) => { + if (!cancelled) { + setDiff(value); + } + }) + .catch((err) => { + if (!cancelled) { + setDiff(""); + setDiffError(getErrorMessage(err)); + } + }) + .finally(() => { + if (!cancelled) { + setDiffLoading(false); + } + }); + + return () => { + cancelled = true; + }; + }, [tab, worktree?.id, worktree?.status, diff, diffLoading]); + async function handleRetry() { if (!ticketId) return; setLoading(true); @@ -214,7 +264,11 @@ export default function TicketDetail() { label: "Developer Report", disabled: !ticket.developer_report, }, - { key: "diff" as const, label: "Diff", disabled: !diff && !worktree }, + { + key: "diff" as const, + label: "Diff", + disabled: !worktree || worktree.status !== "Active", + }, ]; return ( @@ -420,7 +474,19 @@ export default function TicketDetail() { )} - {tab === "diff" && } + {tab === "diff" && ( + <> + {diffLoading ? ( +
Loading diff...
+ ) : diffError ? ( +
+ Could not load diff: {diffError} +
+ ) : ( + + )} + + )}