mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-16 14:49:09 +02:00
* perf(inbox): virtualize notification list (MUL-4474) The inbox notification list rendered every item at once. Each row mounts an avatar + hover card, so a long inbox inflates the tab-switch commit — the same render-amplifier class this issue targets. Extract an InboxList component that virtualizes the rows via react-virtuoso (customScrollParent over the existing overflow-y-auto element, same pattern as the issue-detail timeline). Only the visible window plus a small overscan is mounted; everything else — selection, hover, archive, scroll semantics, the row component and callbacks — is unchanged. Virtualization changes exactly one thing: whether an off-screen row is in the DOM. Slice 2a of MUL-4474 (inbox is the no-DnD surface, done first to prove the Virtuoso + scroll + keyboard harness before the drag surfaces). Draft: must pass the manual zero-functional-change pass on a real Desktop build before merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * perf(board): virtualize board columns (MUL-4474) Each board column rendered every card at once; cards carry pickers, avatars, and a per-issue activity indicator, so a tall column inflates the tab-switch commit. Virtualize the cards within each column via react-virtuoso, using the column's own scroll container as customScrollParent. The dnd-kit droppable stays on the always-mounted column scroll container (merged callback ref feeds both dnd-kit and Virtuoso), and SortableContext still wraps the full id list. So cross-column drops (status/assignee change) and reorder among on-screen cards are unchanged; reordering to an off-screen target relies on drag auto-scroll to mount it — the documented virtualization tradeoff, to be confirmed in the manual pass. The infinite-scroll sentinel rides Virtuoso's Footer slot so loadMore still fires at the bottom, and a per-item pt-2 reproduces the previous space-y-2 gap with padding inside the measured item box. issues-page.test.tsx: mock react-virtuoso to render items inline (jsdom has no layout), and make the useDroppable mock's setNodeRef referentially stable to match real dnd-kit — the board's merged customScrollParent ref would otherwise loop on a fresh ref each render. Slice 2b of MUL-4474 on the shared inbox/list/board/swimlane branch. Draft: requires the manual zero-functional-change pass on a real Desktop build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * perf(list): virtualize issue list rows (MUL-4474) The status-grouped list rendered every row in every expanded section at once; each row carries a sortable, context menu, tooltip, and activity indicator, so a long list inflates the tab-switch commit. Virtualize each expanded section's rows with react-virtuoso, all instances sharing the page's single scroll container as customScrollParent. Everything structural is preserved by construction: the Base UI accordion, sticky status headers, collapse, the per-section useDroppable, the per-section SortableContext, and the load-more sentinel (now Virtuoso's Footer). The Virtuoso only mounts for an expanded section (a collapsed/hidden panel has no viewport to measure). Virtualization changes exactly one thing: whether an off-screen row is in the DOM. issue-surface.test.tsx: mock react-virtuoso inline (jsdom has no layout) so the surface-level loading-semantics assertions still observe the list's rows. Slice 2c of MUL-4474 on the shared branch. Draft: requires the manual zero-functional-change pass on a real Desktop build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * perf(swimlane): virtualize lanes (MUL-4474) The swimlane rendered every lane (each a full row of status cells) at once. Virtualize the vertical lane axis with react-virtuoso over the board's outer scroll box (customScrollParent), so only on-screen lanes stay mounted. Behavior is preserved: pinned lanes keep their leading position, the SortableContext still wraps the lane set for grip-drag reorder (its items are only the non-pinned lane ids), per-cell droppables and per-cell card SortableContexts are unchanged (cells live on mounted lanes), the sticky status header stays above the list, and the per-status load-more sentinels ride Virtuoso's Footer. pt-4 per lane reproduces the previous gap-4. swimlane-view.test.tsx: mock react-virtuoso inline so the ~47 lane/cell/DnD assertions still see the lanes the virtualized list renders. Slice 2d of MUL-4474 on the shared branch — this completes the four surfaces (inbox/board/list/swimlane). Draft: requires the full manual zero-functional-change pass on a real Desktop build before merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): don't pass undefined to Virtuoso `components` (MUL-4474) react-virtuoso seeds its `components` prop with an internal `{}` default; passing `components={undefined}` (which the list and board did when there was no Footer — hasMore false / no column footer) overwrites that default with undefined, so Virtuoso's startup destructure of `EmptyPlaceholder`/`Footer` throws and the surface crashes. jsdom tests mock react-virtuoso so this only surfaced on a real Desktop build (found in manual perf testing). Return a stable module-level empty object instead of undefined. Inbox (omits the prop entirely) and swimlane (always supplies a Footer) never hit this and are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>