Skip to content

[Bug]: Inbox → click issue freezes browser tab on long-timeline issues #1968

@violinhost

Description

@violinhost

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions