Skip to content

Conversation

@976520
Copy link
Owner

@976520 976520 commented Dec 29, 2025

No description provided.

@976520 976520 self-assigned this Dec 29, 2025
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @976520, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on a comprehensive set of improvements across the project, primarily enhancing performance, code quality, and build efficiency. Key changes include adopting modern TypeScript features for better encapsulation, implementing micro-optimizations and caching strategies in the core virtualizer logic, and significantly refactoring React components for conciseness and SSR efficiency. Additionally, build configurations have been fine-tuned for optimal bundle size and dependency management, alongside minor updates to project descriptions and documentation links.

Highlights

  • Core Logic Refinements: Improved encapsulation in Virtualizer and VirtualScrollSource using private class fields, and micro-optimizations for Math.floor operations using bitwise OR | 0 in core calculation logic.
  • Performance Enhancements: Implemented caching for virtual items in the Virtualizer to prevent unnecessary re-creations and introduced more aggressive Terser compression settings across build configurations for smaller bundle sizes.
  • SSR and React Component Optimization: Significant refactoring in InfiniteList.tsx to streamline Server-Side Rendering (SSR) data handling, reduce memoization overhead, and make the component logic more concise.
  • Build Configuration Updates: Standardized and enhanced build configurations (tsup.config.ts) across packages, including updated ES targets and refined external dependency handling for better modularity.
  • Documentation and Marketing: Updated the project's package.json description to better reflect its scope and adjusted documentation links in the VitePress theme.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/ci.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link

📊 Test Coverage Report (vitest)

Package Statements Branches Functions Lines
@scrolloop/core 218/218 (100%) 54/55 (98.18%) 25/25 (100%) 218/218 (100%)
@scrolloop/react 510/817 (62.42%) 101/122 (82.78%) 8/21 (38.09%) 510/817 (62.42%)
@scrolloop/react-native 0/237 (0%) 1/4 (25%) 1/4 (25%) 0/237 (0%)
@scrolloop/shared 0/119 (0%) 1/7 (14.28%) 1/7 (14.28%) 0/119 (0%)

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a variety of changes, including performance optimizations, refactoring to use modern JavaScript features like private fields, and updates to the build configuration. While many of the optimizations, such as caching virtual items, are beneficial, a significant portion of the changes in the React hooks and utility files prioritize code brevity over readability. This has led to code that is difficult to understand and maintain due to single-letter variable names and overly compact logic. Additionally, the new build configurations use unsafe minification options that could introduce subtle bugs. My review focuses on improving maintainability by restoring descriptive naming and questioning the safety of the aggressive minification settings.

Comment on lines +6 to +10
readonly #itemSize: number;

constructor(itemSize: number) {
this.#itemSize = itemSize;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using readonly with a private field (#itemSize) is a great improvement for ensuring immutability and true privacy. This is a good modern practice.

Comment on lines +108 to 134
let virtualItems: VirtualItem[];
if (
this.#prevRenderRange &&
this.#prevVirtualItems &&
this.#prevRenderRange.startIndex === renderRange.startIndex &&
this.#prevRenderRange.endIndex === renderRange.endIndex
) {
virtualItems = this.#prevVirtualItems;
} else {
virtualItems = [];
const startIdx = renderRange.startIndex;
const endIdx = renderRange.endIndex;

for (let i = startIdx; i <= endIdx; i++) {
const start = this.#layoutStrategy.getItemOffset(i);
const size = this.#layoutStrategy.getItemSize(i);
virtualItems.push({
index: i,
start,
size,
end: start + size,
});
}

this.#prevRenderRange = renderRange;
this.#prevVirtualItems = virtualItems;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Caching the virtualItems array and only re-calculating it when the renderRange changes is an excellent performance optimization. This will prevent a lot of unnecessary computation and object creation during scrolling. Great addition!

Comment on lines +45 to +58
const ssrData = useMemo(() => {
if (!isServerSide || !initialData?.length) return null;
const initialPages = new Map<number, T[]>();
const totalPages = Math.ceil(initialData.length / pageSize);

for (let i = 0; i < totalPages; i++) {
const start = i * pageSize;
const end = start + pageSize;
initialPages.set(i, initialData.slice(start, end));
initialPages.set(i, initialData.slice(i * pageSize, (i + 1) * pageSize));
}

initialPagesRef.current = initialPages;
initialTotalRef.current = initialTotal ?? initialData.length;
initialHasMoreRef.current = initialTotal
? initialData.length < initialTotal
: true;
}

const { allItems, pages, loadingPages, hasMore, error, loadPage, retry } =
useInfinitePages({
fetchPage,
pageSize,
initialPage,
onPageLoad,
onError,
});
const total = initialTotal ?? initialData.length;
return {
pages: initialPages,
total,
hasMore: initialTotal ? initialData.length < initialTotal : true,
};
}, [isServerSide, initialData, initialTotal, pageSize]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The refactoring of the server-side rendering logic into the ssrData memoized object is a great improvement. It cleans up the component by centralizing the initial data handling, making the subsequent mergedPages, mergedTotal, and mergedHasMore calculations much cleaner and easier to follow.

Comment on lines +133 to +142
const ps = Math.max(
0,
Math.floor(range.startIndex / pageSize) -
Math.floor(range.endIndex / pageSize)
((range.startIndex / pageSize) | 0) - ((range.endIndex / pageSize) | 0)
);
const prefetchEnd =
Math.floor(range.endIndex / pageSize) +
const pe =
((range.endIndex / pageSize) | 0) +
prefetchThreshold +
Math.ceil(overscan / pageSize);

findMissingPages(prefetchStart, prefetchEnd, mergedPages, loadingPages);

for (let page = prefetchStart; page <= prefetchEnd; page++) {
loadPage(page);
}
findMissingPages(ps, pe, mergedPages, loadingPages);
for (let p = ps; p <= pe; p++) loadPage(p);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variables prefetchStart and prefetchEnd have been renamed to ps and pe. This makes the code harder to understand at a glance. While brevity can be good, it shouldn't come at the cost of clarity, especially for logic that calculates ranges. Please consider reverting to the more descriptive variable names for better maintainability.

Suggested change
const ps = Math.max(
0,
Math.floor(range.startIndex / pageSize) -
Math.floor(range.endIndex / pageSize)
((range.startIndex / pageSize) | 0) - ((range.endIndex / pageSize) | 0)
);
const prefetchEnd =
Math.floor(range.endIndex / pageSize) +
const pe =
((range.endIndex / pageSize) | 0) +
prefetchThreshold +
Math.ceil(overscan / pageSize);
findMissingPages(prefetchStart, prefetchEnd, mergedPages, loadingPages);
for (let page = prefetchStart; page <= prefetchEnd; page++) {
loadPage(page);
}
findMissingPages(ps, pe, mergedPages, loadingPages);
for (let p = ps; p <= pe; p++) loadPage(p);
const prefetchStart = Math.max(
0,
((range.startIndex / pageSize) | 0) - ((range.endIndex / pageSize) | 0)
);
const prefetchEnd =
((range.endIndex / pageSize) | 0) +
prefetchThreshold +
Math.ceil(overscan / pageSize);
findMissingPages(prefetchStart, prefetchEnd, mergedPages, loadingPages);
for (let p = prefetchStart; p <= prefetchEnd; p++) loadPage(p);

Comment on lines +165 to 210
const commonContainerStyle: CSSProperties = {
height,
display: "flex",
alignItems: "center",
justifyContent: "center",
};
const heightOnlyStyle: CSSProperties = { height };

if (error && mergedAllItems.length === 0) {
if (error && !mergedAllItems.length) {
if (renderError)
return <div style={heightOnlyStyle}>{renderError(error, retry)}</div>;
return (
<div style={errorContainerStyle}>
<div style={errorContentStyle}>
<div style={commonContainerStyle}>
<div style={{ textAlign: "center" }}>
<p>Error.</p>
<p style={errorMessageStyle}>{error.message}</p>
<button onClick={retry} style={retryButtonStyle}>
<p style={{ color: "#666", fontSize: "0.9em" }}>{error.message}</p>
<button
onClick={retry}
style={{ marginTop: 8, padding: "4px 12px", cursor: "pointer" }}
>
Retry
</button>
</div>
</div>
);
}

if (mergedAllItems.length === 0 && loadingPages.size > 0) {
if (renderLoading) {
return <div style={heightOnlyStyle}>{renderLoading()}</div>;
}
return (
<div style={loadingContainerStyle}>
if (!mergedAllItems.length && loadingPages.size) {
return renderLoading ? (
<div style={heightOnlyStyle}>{renderLoading()}</div>
) : (
<div style={commonContainerStyle}>
<p>Loading...</p>
</div>
);
}

if (mergedAllItems.length === 0 && !mergedHasMore) {
if (renderEmpty) {
return <div style={heightOnlyStyle}>{renderEmpty()}</div>;
}
return (
<div style={emptyContainerStyle}>
if (!mergedAllItems.length && !mergedHasMore) {
return renderEmpty ? (
<div style={heightOnlyStyle}>{renderEmpty()}</div>
) : (
<div style={commonContainerStyle}>
<p>No data.</p>
</div>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Many useMemo hooks for styles have been removed and replaced with inline style objects or a single commonContainerStyle object. This means these style objects are recreated on every render. For static styles, it's better to define them as constants outside the component. For styles dependent on props (like height), useMemo was appropriate to prevent re-creating the object if height didn't change. This change could lead to performance degradation due to unnecessary re-renders in child components. Consider moving static styles outside the component and re-introducing useMemo for dynamic styles.

@github-actions
Copy link

📊 Test Coverage Report (vitest)

Package Statements Branches Functions Lines
@scrolloop/core 218/218 (100%) 54/55 (98.18%) 25/25 (100%) 218/218 (100%)
@scrolloop/react 510/817 (62.42%) 101/122 (82.78%) 8/21 (38.09%) 510/817 (62.42%)
@scrolloop/react-native 0/237 (0%) 1/4 (25%) 1/4 (25%) 0/237 (0%)
@scrolloop/shared 0/119 (0%) 1/7 (14.28%) 1/7 (14.28%) 0/119 (0%)

@github-actions
Copy link

📊 Test Coverage Report (vitest)

Package Statements Branches Functions Lines
@scrolloop/core 218/218 (100%) 54/55 (98.18%) 25/25 (100%) 218/218 (100%)
@scrolloop/react 510/817 (62.42%) 101/122 (82.78%) 8/21 (38.09%) 510/817 (62.42%)
@scrolloop/react-native 0/237 (0%) 1/4 (25%) 1/4 (25%) 0/237 (0%)
@scrolloop/shared 0/119 (0%) 1/7 (14.28%) 1/7 (14.28%) 0/119 (0%)

@976520 976520 merged commit a92d19b into master Dec 29, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants