Compare commits

...

2 Commits

Author SHA1 Message Date
Jiayuan
0c09ff9851 fix(inbox): use highlightCommentId as useLayoutEffect dependency
The empty dependency array meant the layout effect only ran on mount.
When switching between different inbox notifications for the same issue,
the component doesn't remount (keyed by issue_id), so the scroll-to-
activity wouldn't re-fire. Using [highlightCommentId] ensures it runs
whenever the target comment changes.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-01 09:49:16 +02:00
Jiayuan
c7373ed4bb fix(inbox): scroll to activity section immediately to avoid description flash
When clicking an Inbox notification with a target comment, the issue
detail page would first show the description at scroll position 0, then
jump to the comment only after the timeline loaded asynchronously. This
caused a visible flash of the description before the comment appeared.

Use useLayoutEffect to scroll the activity section into view before the
browser paints, so the user lands on the activity area immediately. The
existing useEffect then scrolls precisely to the specific comment once
the timeline loads and the DOM element is available.

Closes MUL-1669

Co-authored-by: multica-agent <github@multica.ai>
2026-05-01 09:45:17 +02:00

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { useState, useEffect, useLayoutEffect, useCallback, useRef } from "react";
import { useDefaultLayout, usePanelRef } from "react-resizable-panels";
import { AppLink } from "../../navigation";
import { useNavigation } from "../../navigation";
@@ -194,6 +194,7 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr
const scrollContainerRef = useRef<HTMLDivElement>(null);
const [highlightedId, setHighlightedId] = useState<string | null>(null);
const didHighlightRef = useRef<string | null>(null);
const activitySectionRef = useRef<HTMLDivElement>(null);
// Issue data from TQ — uses detail query, seeded from list cache if available.
// Only seed when description is present; list API omits it, and ContentEditor
@@ -293,6 +294,15 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr
}
}, [highlightCommentId, timeline.length]);
// When opening from inbox with a target comment, scroll past the description
// to the activity section before the browser paints, so the user never sees
// the description flash. The precise scroll to the specific comment happens
// in the useEffect above once the timeline has loaded.
useLayoutEffect(() => {
if (!highlightCommentId) return;
activitySectionRef.current?.scrollIntoView({ behavior: "instant", block: "start" });
}, [highlightCommentId]);
const descEditorRef = useRef<ContentEditorRef>(null);
const { isDragOver: descDragOver, dropZoneProps: descDropZoneProps } = useFileDropZone({
onDrop: (files) => files.forEach((f) => descEditorRef.current?.uploadFile(f)),
@@ -798,7 +808,7 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr
<div className="my-8 border-t" />
{/* Activity / Comments */}
<div>
<div ref={activitySectionRef}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<h2 className="text-base font-semibold">Activity</h2>