fix(ui): avoid freeze on notification click

This commit is contained in:
thibaud-lclr 2026-04-20 09:25:31 +02:00
parent 62b381b844
commit 1b751ac16d
2 changed files with 97 additions and 31 deletions

View file

@ -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;

View file

@ -58,8 +58,11 @@ export default function TicketDetail() {
const [targetBranch, setTargetBranch] = useState("");
const [availableBranches, setAvailableBranches] = useState<string[]>([]);
const [branchInputMode, setBranchInputMode] = useState<"select" | "manual">("select");
const [branchesLoadedForWorktreeId, setBranchesLoadedForWorktreeId] = useState<string | null>(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() {
</div>
)}
{tab === "diff" && <DiffViewer diff={diff || ""} />}
{tab === "diff" && (
<>
{diffLoading ? (
<div className="p-4 text-sm text-gray-500">Loading diff...</div>
) : diffError ? (
<div className={noticeClass("warning")}>
Could not load diff: {diffError}
</div>
) : (
<DiffViewer diff={diff || ""} />
)}
</>
)}
<ConfirmModal
isOpen={isDeleteWorktreeModalOpen}