Deployment type
Self-hosted
What happened?
Version observed: v0.2.22 (commit befde379)
Summary
Opening an issue from Inbox that has a large timeline (thousands of comments + activities) hard-freezes the browser tab on a main-thread block. The same issue opened via the regular issue list is laggy but does not freeze.
The Inbox-embedded IssueDetail render path is the cause.
Root cause
1. Inbox embeds IssueDetail directly (no nav)
packages/views/inbox/components/inbox-page.tsx (≈ line 267):
<IssueDetail
key={selected.issue_id}
issueId={selected.issue_id}
highlightCommentId={selected.details?.comment_id ?? undefined}
...
/>
Inbox renders IssueDetail inside its own pane. Every Inbox state update (including WebSocket-driven inbox cache updates) reconciles the embedded detail subtree.
2. Timeline is rendered without virtualization
packages/views/issues/components/issue-detail.tsx (≈ line 880):
return groups.map((group) => {
if (group.type === "comment") {
return <div key={entry.id} id={`comment-${entry.id}`}>
<CommentCard ... />
</div>;
}
...
});
Plain .map() over every coalesced entry — no windowing. Thousands of CommentCard components mount on first paint.
3. Each CommentCard runs a heavy markdown pipeline
From packages/views/editor/readonly-content.tsx:
import ReactMarkdown from "react-markdown";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import remarkBreaks from "remark-breaks";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { createLowlight, common } from "lowlight";
const lowlight = createLowlight(common); // ~30 grammars
Every comment runs the full react-markdown + rehype-raw + rehype-sanitize + rehype-katex + lowlight (common) pipeline. Multiplying this by N = thousands is the freeze.
4. CommentCard is not memoized
comment-card.tsx exports a plain function component, no React.memo. Any state change in IssueDetail (highlight state, query refresh from useIssueTimeline) re-renders every comment.
5. No virtualization lib in the project
$ grep -E 'virtuoso|react-window|@tanstack/react-virtual|virtua' apps/web/package.json packages/views/package.json
# no matches
Note on PR #1887
PR #1887 (fix(inbox): jump instantly to targeted comments) changed the highlight scroll from behavior: "smooth" to behavior: "instant" at issue-detail.tsx:287. That eliminated smooth-scroll cost, but the dominant cost is the
synchronous render of N timeline entries, which is unaffected.
Why Inbox triggers it harder than the standalone issue route
- Standalone route /<slug>/issues/<id> mounts IssueDetail via navigation; the cost smears across cold-start frames and the URL change cleanly resets the tree.
- Inbox keeps the inbox list rendered alongside the embedded IssueDetail, and inbox WS events keep updating the parent. The bulk render competes with WS-driven updates on the same page.
- inbox-page.tsx keys IssueDetail by selected.issue_id (correct — to preserve the comment-composer draft), but that means the same long timeline gets reconciled on every new inbox event for that issue.
Suggested fixes
S1 — cap on first paint (smallest): render only the most recent N (~50) timeline entries, with a "Show older N entries" button. Single-file change in issue-detail.tsx.
S2 — virtualize the timeline: adopt react-virtuoso / @tanstack/react-virtual / virtua so out-of-viewport CommentCards are not in the React tree.
S3 — memoize render cost: wrap CommentCard in React.memo keyed on entry.id + entry.updated_at + highlightedCommentId; memoize ReadonlyContent output by (content, mentions) to avoid re-walking the markdown AST on unrelated
re-renders.
S4 — Inbox fallback for heavy issues: if timeline length exceeds a threshold (e.g. 200), have Inbox link to the standalone issue page instead of embedding IssueDetail.
### Steps to reproduce
1. Create an issue and post a large number of comments on it (e.g. via API / scripted automation).
Reproducing values that trigger the freeze on my install:
comments = ~3,800
activities = ~2,600
timeline total = ~6,400 entries
2. Trigger an Inbox notification for that issue (a new comment / status change is enough).
3. Click the Inbox notification → page locks; browser shows "page unresponsive" on Chrome.
4. Open the same issue from the regular issue list → noticeably laggy on first paint, but the tab stays alive.
### Screenshots (optional)
_No response_
### Additional context (optional)
```shell
Deployment type
Self-hosted
What happened?
Version observed: v0.2.22 (commit
befde379)Summary
Opening an issue from Inbox that has a large timeline (thousands of comments + activities) hard-freezes the browser tab on a main-thread block. The same issue opened via the regular issue list is laggy but does not freeze.
The Inbox-embedded
IssueDetailrender path is the cause.Root cause
1. Inbox embeds
IssueDetaildirectly (no nav)packages/views/inbox/components/inbox-page.tsx(≈ line 267):