Files
multica/pnpm-workspace.yaml
Naiyuan Qing fb8ad8cc5e perf: virtualize issue detail timeline + seed test scaffolding (#2413)
* perf(views): virtualize issue detail timeline with react-virtuoso

The unvirtualized timeline at issue-detail.tsx full-mounted every
entry, freezing first paint for several seconds at 500+ comments
(markdown parse + lowlight per CommentCard on mount). Production p99
is ~30 comments but the all-time max is ~1.1k and the server hard-caps
at 2000 — long-tail issues were unusable.

Swap the inline `.map` for `<Virtuoso customScrollParent>` driven by a
flattened TimelineItem discriminated union. TanStack Query stays the
source of truth; existing memo machinery (`prevThreadRepliesRef`,
`EMPTY_REPLIES`) and WS handlers are untouched. `followOutput="auto"`
matches Slack/Discord — users at the bottom auto-follow new comments,
users mid-scroll are not yanked back down.

Comment drafts move to a new persisted Zustand store
(`comment-draft-store`) so virtualization-driven unmount can no longer
drop in-progress edits or new comments. Hydrates via ContentEditor
`defaultValue`, flushes on update / blur / visibilitychange.

Deep-link from inbox is rewritten from `getElementById` +
`scrollIntoView` to `virtuosoRef.scrollToIndex` with a double-rAF
mitigation for the Virtuoso #883 initial-scroll race. Highlight flash
bumped 2s→3s to outlast mount latency on cold cards.

Cmd-F shows a once-per-session toast on long timelines since browser
find-in-page can't reach off-screen virtualized items. Real in-app
search lands in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(views): repair deep-link scroll and isolate comment drafts

The first virtualization landing had three latent issues that runtime
testing on perf fixtures (10 → 5000 comments) exposed:

1. Deep-link landing position was wrong by ~380px on every issue.
   In customScrollParent mode Virtuoso computes scrollTop from the
   list's internal coordinate space only — it doesn't account for
   sibling content (title editor, description, sub-issues, agent
   card) sitting above the list inside the same scroll parent. The
   useEffect now uses Virtuoso scrollToIndex only to MOUNT the
   target into the DOM, then polls a `data-comment-id` anchor and
   delegates positioning to the browser's scrollIntoView, which
   honors getBoundingClientRect and lands accurately every time.

2. Scroll-up was being yanked back to the deep-link anchor on every
   ResizeObserver tick. Root cause was `followOutput="auto"`, which
   stays "stuck to bottom" once the deep-link lands there and resets
   scrollTop to maxScrollTop on each height change. Issue detail is
   document-shaped, not chat-shaped, so removing followOutput
   altogether is the right tradeoff. Likewise `initialTopMostItemIndex`
   acts as a persistent anchor in customScrollParent mode (Virtuoso
   #458) — dropped entirely and replaced with imperative scroll.
   `defaultItemHeight` is also dropped so Virtuoso probes real
   heights instead of estimating + correcting visually.

3. Reply-comment deep-links from the inbox would short-circuit
   because the reply id isn't in the flat items[] array. Added a
   replyToRoot map so deep-link falls back to the enclosing thread's
   root index, scrolls there, and lets the reply's own ring fire
   once the thread is in view.

Also fixes a latent cross-issue draft leak in `<CommentInput>`:
web's /issues/[id] route doesn't remount IssueDetail on issueId
change, so without an explicit `key={id}` the editor kept the
previous issue's in-memory content and the next keystroke would
flush it under the new issue's draft key. The same fix incidentally
repairs the pre-existing "submit composer from issue A while viewing
issue B" submit-target bug.

Highlight UX polish: bg-brand/5 was too faint to notice; ring upgraded
to ring-brand/60 as the sole signal. transition-colors didn't actually
animate ring/box-shadow — switched to transition-shadow duration-500
ease-out so highlight has visible fade in / fade out. Flash duration
3s → 4s. Polling failure now still sets highlight + warns so a manual
scroll to the target still flashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:56:27 +08:00

62 lines
1.3 KiB
YAML

packages:
- "apps/*"
- "packages/*"
catalog:
# Core React
react: "19.2.3"
react-dom: "19.2.3"
"@types/react": "^19.2.0"
"@types/react-dom": "^19.2.0"
# TypeScript & Node
typescript: "^5.9.3"
"@types/node": "^25.0.10"
# State Management
zustand: "^5.0.0"
"@tanstack/react-query": "^5.96.2"
"@tanstack/react-table": "^8.21.3"
# Runtime schema validation (defensive boundary against API drift —
# see CLAUDE.md "API Response Compatibility")
zod: "^4.1.5"
# UI & Styling
tailwindcss: "^4"
"@tailwindcss/postcss": "^4"
"@tailwindcss/vite": "^4"
tailwind-merge: "^3.4.0"
class-variance-authority: "^0.7.1"
clsx: "^2.1.1"
katex: "^0.16.45"
rehype-katex: "^7.0.1"
remark-math: "^6.0.0"
mermaid: "^11.14.0"
# Icons
lucide-react: "^1.0.1"
# i18n
i18next: "^26.0.8"
react-i18next: "^17.0.6"
"@formatjs/intl-localematcher": "^0.8.4"
eslint-plugin-i18next: "^6.1.4"
# Loading animations (chat StatusPill)
unicode-animations: "^1.0.3"
# Virtualized timeline (issue detail comments)
react-virtuoso: "^4.14.0"
# Product analytics
posthog-js: "^1.176.1"
# Testing
vitest: "^4.1.0"
jsdom: "^29.0.1"
"@vitejs/plugin-react": "^6.0.1"
"@testing-library/react": "^16.3.2"
"@testing-library/jest-dom": "^6.9.1"
"@testing-library/user-event": "^14.6.1"