Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/views/editor/readonly-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* - Rendering mentions with the same IssueMentionCard component and .mention class
*/

import { isValidElement, useEffect, useId, useMemo, useRef, useState } from "react";
import { isValidElement, memo, useEffect, useId, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import ReactMarkdown, {
defaultUrlTransform,
Expand Down Expand Up @@ -573,7 +573,7 @@ interface ReadonlyContentProps {
className?: string;
}

export function ReadonlyContent({ content, className }: ReadonlyContentProps) {
export const ReadonlyContent = memo(function ReadonlyContent({ content, className }: ReadonlyContentProps) {
const processed = useMemo(() => preprocessMarkdown(content), [content]);
const wrapperRef = useRef<HTMLDivElement>(null);
const hover = useLinkHover(wrapperRef);
Expand All @@ -591,4 +591,4 @@ export function ReadonlyContent({ content, className }: ReadonlyContentProps) {
<LinkHoverCard {...hover} />
</div>
);
}
});
6 changes: 3 additions & 3 deletions packages/views/issues/components/comment-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useCallback, useRef, useState } from "react";
import { memo, useCallback, useRef, useState } from "react";
import { ChevronRight, Copy, Download, FileText, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { Card } from "@multica/ui/components/ui/card";
Expand Down Expand Up @@ -348,7 +348,7 @@ function CommentRow({
// CommentCard — One Card per thread (parent + all replies flat inside)
// ---------------------------------------------------------------------------

function CommentCard({
const CommentCard = memo(function CommentCard({
issueId,
entry,
allReplies,
Expand Down Expand Up @@ -604,6 +604,6 @@ function CommentCard({
</Collapsible>
</Card>
);
}
});

export { CommentCard, type CommentCardProps };
32 changes: 30 additions & 2 deletions packages/views/issues/components/issue-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ 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 [showAllTimeline, setShowAllTimeline] = useState(false);
const INITIAL_VISIBLE_GROUPS = 50;

// 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
Expand Down Expand Up @@ -276,6 +278,12 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr

const loading = issueLoading;

// When a highlight target is specified, expand the full timeline so the
// comment is guaranteed to be rendered before we attempt to scroll to it.
useEffect(() => {
if (highlightCommentId) setShowAllTimeline(true);
}, [highlightCommentId]);

// Scroll to highlighted comment once timeline loads (fire only once per highlightCommentId)
useEffect(() => {
if (!highlightCommentId || timeline.length === 0) return;
Expand Down Expand Up @@ -924,7 +932,12 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr
}
}

return groups.map((group) => {
// Truncate: show only the most recent groups initially
const isTruncated = !showAllTimeline && groups.length > INITIAL_VISIBLE_GROUPS;
const hiddenCount = isTruncated ? groups.length - INITIAL_VISIBLE_GROUPS : 0;
const visibleGroups = isTruncated ? groups.slice(-INITIAL_VISIBLE_GROUPS) : groups;

const renderGroup = (group: typeof groups[number]) => {
if (group.type === "comment") {
const entry = group.entries[0]!;
return (
Expand Down Expand Up @@ -990,7 +1003,22 @@ export function IssueDetail({ issueId, onDelete, onDone, defaultSidebarOpen = tr
})}
</div>
);
});
};

return (
<>
{isTruncated && (
<button
type="button"
className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-border py-2 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
onClick={() => setShowAllTimeline(true)}
>
Show {hiddenCount} earlier {hiddenCount === 1 ? "entry" : "entries"}
</button>
)}
{visibleGroups.map(renderGroup)}
</>
);
})()}
</div>

Expand Down
Loading