Skip to content

Conversation

@shivraj2805
Copy link

@shivraj2805 shivraj2805 commented Jul 26, 2025

🛠️ What does this PR do?

This PR implements scroll position restoration in the document list. When a user opens a document and then returns to the list, the scroll position remains the same instead of jumping to the top.

📂 Files Changed

  • useScrollRestoration.ts: Created a custom hook to manage scroll position.
  • ui-state.ts: Added state handling for scroll position.
  • Updated the Docs List component to use this hook.

✅ How to Test

  1. Navigate to the doc list and scroll down.
  2. Open any document.
  3. Click back to return to the list.
  4. Scroll position should be restored instead of jumping to the top.

📌 Related Issue

Fixes #13314

Summary by CodeRabbit

  • New Features
    • Document list now remembers and restores your scroll position when navigating away and returning.
  • Style
    • Minor improvements to code readability and structure.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 26, 2025

Walkthrough

The changes implement scroll position persistence for the document list in the DocsExplorer component. A new hook (useScrollRestoration) and supporting module manage scroll state, with getter and setter functions exported for use elsewhere. The document list component is refactored to utilize these utilities, enabling the list to remember its scroll position when navigating away and back.

Changes

File(s) Change Summary
packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx Refactored to add scroll position persistence using a container ref, new effect, and exported getter/setter.
packages/frontend/core/src/hooks/useScrollRestoration.ts Introduced new hook for scroll restoration using sessionStorage and element refs.
packages/frontend/core/src/modules/doc/stores/ui-state.ts Added module to store and manage scroll position with exported getter and setter functions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DocsListComponent
    participant ScrollStateModule
    participant useScrollRestoration

    User->>DocsListComponent: Mounts Docs List
    DocsListComponent->>useScrollRestoration: Initialize with ref and key
    useScrollRestoration->>ScrollStateModule: Retrieve scroll position
    ScrollStateModule-->>useScrollRestoration: Return last scroll position
    useScrollRestoration->>DocsListComponent: Restore scroll position

    User->>DocsListComponent: Navigates away
    DocsListComponent->>useScrollRestoration: Cleanup
    useScrollRestoration->>ScrollStateModule: Save current scroll position
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested labels

app:core

Poem

A scroll, a hop, a leap through docs,
Now the list recalls where bunny walks.
No more jumping back to top—
Your place is saved, you never stop!
With memory keen and whiskers proud,
The rabbit cheers this change aloud.
🐰✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/frontend/core/src/modules/doc/stores/ui-state.ts (1)

3-7: Consider session persistence for better user experience.

The current module-level variable approach doesn't persist scroll position across page reloads or browser sessions. Users would lose their scroll position if they refresh the page.

Consider using sessionStorage or localStorage for persistence, similar to the useScrollRestoration hook implementation.

-let lastScrollPosition = 0;
+const STORAGE_KEY = 'docs-list-scroll-position';

-export const getDocsListScrollPosition = () => lastScrollPosition;
+export const getDocsListScrollPosition = () => {
+  const stored = sessionStorage.getItem(STORAGE_KEY);
+  return stored ? parseInt(stored, 10) : 0;
+};

-export const setDocsListScrollPosition = (pos: number) => {
-  lastScrollPosition = pos;
-};
+export const setDocsListScrollPosition = (pos: number) => {
+  sessionStorage.setItem(STORAGE_KEY, pos.toString());
+};
packages/frontend/core/src/hooks/useScrollRestoration.ts (1)

4-23: Consider using this hook instead of the current implementation.

This useScrollRestoration hook provides a more robust solution with session persistence compared to the current module-level approach used in docs-list.tsx. However, it's not being utilized in the codebase.

Consider integrating this hook in the DocsExplorer component to replace the current scroll restoration logic.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be55442 and 5511b2b.

📒 Files selected for processing (3)
  • packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx (10 hunks)
  • packages/frontend/core/src/hooks/useScrollRestoration.ts (1 hunks)
  • packages/frontend/core/src/modules/doc/stores/ui-state.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx (1)

Learnt from: EYHN
PR: #12320
File: packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx:366-366
Timestamp: 2025-05-22T02:37:07.462Z
Learning: In React components, avoid redundant translation calls. When using services like docDisplayMetaService.title$ that already provide localized strings, don't wrap the result in another translation call like i18n.t() as it's unnecessary and may break interpolation if the string contains special markers.

🔇 Additional comments (10)
packages/frontend/core/src/hooks/useScrollRestoration.ts (1)

18-21: Remove redundant cleanup call in useEffect return.

The handleBeforeUnload() call in the cleanup function is redundant since the same function is already registered as a beforeunload event listener. This could cause the scroll position to be saved twice.

    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => {
-      handleBeforeUnload();
      window.removeEventListener('beforeunload', handleBeforeUnload);
    };

Likely an incorrect or invalid review comment.

packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx (9)

12-12: LGTM!

The addition of useRef import is necessary for the scroll container reference implementation.


72-72: Good code simplification.

Removing unnecessary braces improves readability while maintaining the same logic.


79-79: Good defensive programming practice.

Adding the null check for id prevents potential runtime errors when charCodeAt(0) is called on undefined/null values.


130-130: Minor code style improvement noted.

Simplifying the parameter name from (group: any) improves readability, though the any type could be more specific if the group type is available.


164-169: Code style improvements noted.

The removal of unnecessary braces and early returns make the code more concise and readable.


177-179: Template string formatting improvement.

The multiline template string formatting improves readability.


186-190: Variable extraction improves code clarity.

Extracting selected variable and simplifying the loop variable name enhances readability.


205-208: Consistent variable extraction pattern.

The same variable extraction pattern is applied consistently, improving code maintainability.


251-254: Scroll container implementation is correct.

The div wrapper with scroll properties correctly enables the scroll restoration functionality.

Comment on lines +231 to +244
const scrollContainerRef = useRef<HTMLDivElement>(null);

// ✅ Scroll restore logic
useEffect(() => {
const container = scrollContainerRef.current;
if (container) {
container.scrollTop = getDocsListScrollPosition();
}
return () => {
if (container) {
setDocsListScrollPosition(container.scrollTop);
}
};
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Scroll restoration implementation needs architectural review.

The scroll restoration logic is functionally correct but has some concerns:

  1. Code duplication: This duplicates the exact same functionality as ui-state.ts
  2. Memory persistence: Uses module-level state that doesn't persist across page reloads
  3. Unused hook: The useScrollRestoration hook provides better functionality but isn't used

Consider using the useScrollRestoration hook instead:

-  const scrollContainerRef = useRef<HTMLDivElement>(null);
-
-  // ✅ Scroll restore logic
-  useEffect(() => {
-    const container = scrollContainerRef.current;
-    if (container) {
-      container.scrollTop = getDocsListScrollPosition();
-    }
-    return () => {
-      if (container) {
-        setDocsListScrollPosition(container.scrollTop);
-      }
-    };
-  }, []);
+  const scrollContainerRef = useRef<HTMLDivElement>(null);
+  useScrollRestoration('docs-list', scrollContainerRef);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const scrollContainerRef = useRef<HTMLDivElement>(null);
// ✅ Scroll restore logic
useEffect(() => {
const container = scrollContainerRef.current;
if (container) {
container.scrollTop = getDocsListScrollPosition();
}
return () => {
if (container) {
setDocsListScrollPosition(container.scrollTop);
}
};
}, []);
const scrollContainerRef = useRef<HTMLDivElement>(null);
useScrollRestoration('docs-list', scrollContainerRef);
🤖 Prompt for AI Agents
In packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx around
lines 231 to 244, the current scroll restoration logic duplicates functionality
found in ui-state.ts, uses non-persistent module-level state, and does not
leverage the existing useScrollRestoration hook. To fix this, remove the manual
scroll restoration code and replace it by importing and using the
useScrollRestoration hook, which provides improved and persistent scroll
restoration behavior across page reloads.

Comment on lines +295 to +301
let lastScrollPosition = 0;

export const getDocsListScrollPosition = () => lastScrollPosition;

export const setDocsListScrollPosition = (pos: number) => {
lastScrollPosition = pos;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicated scroll state management.

This code duplicates the exact same functionality as the ui-state.ts module, creating maintenance overhead and potential inconsistencies.

Since ui-state.ts already provides these functions, remove this duplication:

-// ✅ Scroll state management
-let lastScrollPosition = 0;
-
-export const getDocsListScrollPosition = () => lastScrollPosition;
-
-export const setDocsListScrollPosition = (pos: number) => {
-  lastScrollPosition = pos;
-};

And import from the centralized module instead:

+import { getDocsListScrollPosition, setDocsListScrollPosition } from '@affine/core/modules/doc/stores/ui-state';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let lastScrollPosition = 0;
export const getDocsListScrollPosition = () => lastScrollPosition;
export const setDocsListScrollPosition = (pos: number) => {
lastScrollPosition = pos;
};
import { getDocsListScrollPosition, setDocsListScrollPosition } from '@affine/core/modules/doc/stores/ui-state';
🤖 Prompt for AI Agents
In packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx around
lines 295 to 301, remove the duplicated scroll state management functions
getDocsListScrollPosition and setDocsListScrollPosition. Instead, import these
functions from the existing centralized ui-state.ts module to avoid redundancy
and maintain consistency.

Comment on lines +6 to +9
const savedY = sessionStorage.getItem(`scroll-${key}`);
if (ref.current && savedY) {
ref.current.scrollTo(0, parseInt(savedY, 10));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and input validation for sessionStorage operations.

The current implementation doesn't handle potential sessionStorage errors or validate the parsed integer value, which could cause runtime issues.

  useEffect(() => {
-    const savedY = sessionStorage.getItem(`scroll-${key}`);
-    if (ref.current && savedY) {
-      ref.current.scrollTo(0, parseInt(savedY, 10));
-    }
+    try {
+      const savedY = sessionStorage.getItem(`scroll-${key}`);
+      if (ref.current && savedY) {
+        const scrollPosition = parseInt(savedY, 10);
+        if (!isNaN(scrollPosition) && scrollPosition >= 0) {
+          ref.current.scrollTo(0, scrollPosition);
+        }
+      }
+    } catch (error) {
+      console.warn('Failed to restore scroll position:', error);
+    }
🤖 Prompt for AI Agents
In packages/frontend/core/src/hooks/useScrollRestoration.ts around lines 6 to 9,
add try-catch blocks around the sessionStorage.getItem call to handle potential
errors accessing sessionStorage. Also, validate that the parsed integer from
savedY is a valid number before calling scrollTo, and only perform the scroll if
the value is a valid integer to prevent runtime issues.

Comment on lines +11 to +15
const handleBeforeUnload = () => {
if (ref.current) {
sessionStorage.setItem(`scroll-${key}`, ref.current.scrollTop.toString());
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for sessionStorage write operations.

Similar to the read operation, the write operation should handle potential sessionStorage errors.

    const handleBeforeUnload = () => {
      if (ref.current) {
-        sessionStorage.setItem(`scroll-${key}`, ref.current.scrollTop.toString());
+        try {
+          sessionStorage.setItem(`scroll-${key}`, ref.current.scrollTop.toString());
+        } catch (error) {
+          console.warn('Failed to save scroll position:', error);
+        }
      }
    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleBeforeUnload = () => {
if (ref.current) {
sessionStorage.setItem(`scroll-${key}`, ref.current.scrollTop.toString());
}
};
const handleBeforeUnload = () => {
if (ref.current) {
try {
sessionStorage.setItem(`scroll-${key}`, ref.current.scrollTop.toString());
} catch (error) {
console.warn('Failed to save scroll position:', error);
}
}
};
🤖 Prompt for AI Agents
In packages/frontend/core/src/hooks/useScrollRestoration.ts around lines 11 to
15, the sessionStorage.setItem call inside handleBeforeUnload lacks error
handling. Wrap the sessionStorage.setItem call in a try-catch block to catch and
handle any exceptions that may occur during the write operation, ensuring the
app does not crash and errors can be logged or handled gracefully.

Comment on lines +1 to +7
let lastScrollPosition = 0;

export const getDocsListScrollPosition = () => lastScrollPosition;

export const setDocsListScrollPosition = (pos: number) => {
lastScrollPosition = pos;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Code duplication detected with docs-list.tsx implementation.

This module provides identical functionality to the scroll state management implemented directly in docs-list.tsx (lines 295-301). Having two separate implementations for the same purpose creates maintenance overhead and potential inconsistencies.

Consider consolidating to a single approach - either use this centralized module consistently or remove it in favor of the component-level implementation.

🤖 Prompt for AI Agents
In packages/frontend/core/src/modules/doc/stores/ui-state.ts lines 1 to 7, the
scroll position state management duplicates the implementation found in
docs-list.tsx lines 295 to 301. To fix this, choose one implementation to keep
and remove the other to avoid redundancy. If you keep this module, refactor
docs-list.tsx to use these exported functions for scroll position management;
otherwise, delete this module and rely solely on the component-level code.

Comment on lines +231 to +244
const scrollContainerRef = useRef<HTMLDivElement>(null);

// ✅ Scroll restore logic
useEffect(() => {
const container = scrollContainerRef.current;
if (container) {
container.scrollTop = getDocsListScrollPosition();
}
return () => {
if (container) {
setDocsListScrollPosition(container.scrollTop);
}
};
}, []);
Copy link
Member

Choose a reason for hiding this comment

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

should use useScrollRestoration

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Feature Request]: Remain the list position when back from the content to the list

2 participants