Skip to content

Conversation

@HellowVirgil
Copy link
Contributor

@HellowVirgil HellowVirgil commented Nov 18, 2025

Types

  • 🎉 New Features
  • 🐛 Bug Fixes
  • 📚 Documentation Changes
  • 💄 Code Style Changes
  • 💄 Style Changes
  • 🪚 Refactors
  • 🚀 Performance Improvements
  • 🏗️ Build System
  • ⏱ Tests
  • 🧹 Chores
  • Other Changes

Background or solution

当前 .ipynb 文件(Notebook 文件)使用 LibroOpensumiView 组件,这是一个基于 Libro 框架的自定义视图
Notebook 的状态管理在 Libro 框架内部,而不是 OpenSumi 的标准编辑器状态系统
当前Notebook 编辑器没有实现类似的状态保存/恢复机制,Libro 框架的视图状态没有与 OpenSumi 的编辑器状态系统集成,所以当切换 .ipynb 文件时,整个 LibroView 会被重新创建,而不是恢复之前的状态。

Changelog

新增 libro-state-manager 来对 libro 视图进行状态管理,实现多个 ipynb 文件之间切换能够保留位置。

2025-11-18.19.44.16.mov

Summary by CodeRabbit

发布说明

  • 新功能
    • 添加笔记本视图状态持久化:按 URI 保存每个笔记本的滚动位置与活跃时间
    • 重新打开笔记本时自动恢复到上次离开的位置
    • 在笔记本加载完成后恢复状态以提升一致性
    • 集成防抖滚动事件(500ms)以减少频繁保存,提升性能

✏️ Tip: You can customize this high-level summary in your review settings.

@coffeedeveloper
Copy link
Contributor

/next

@HellowVirgil HellowVirgil force-pushed the feat/keep-notebook-switch-position branch from cf5911c to bdfd315 Compare November 18, 2025 11:51
@coffeedeveloper
Copy link
Contributor

/next

@opensumi opensumi bot added the 🎨 feature feature required label Nov 18, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

新增 LibroStateManager 服务及其 DI 提供器,用于按 URI 持久化与恢复笔记本视图状态(滚动位置、最后活跃时间);在贡献点注册提供者并在 libro 视图中绑定保存/恢复逻辑与防抖滚动监听器(500ms)。

Changes

内聚组 / 文件(s) 变更摘要
状态管理核心
packages/notebook/src/browser/libro-state-manager.ts
新增文件。引入 INotebookViewStateINotebookStateManager 接口与 LibroStateManager 实现。包含内存缓存、storageProvider 读写、loadFromStorage/saveToStorage、类型守卫 isValidStatesaveState/restoreState/clearState/getAllStates/getState 等方法,以及静态选择器常量 LIBRO_SCROLLER_SELECTOR。基础错误处理通过 console 警告。
依赖注入配置
packages/notebook/src/browser/libro-state.ts
新增文件。导出 LibroStateModule: Provider[],将 LibroStateManager 注册为可注入提供者(token → useClass)。
模块集成
packages/notebook/src/browser/libro.contribution.tsx
在贡献点引入并注册 LibroStateModule:在 initialize 中调用 app.injector.addProviders(...LibroStateModule)
视图状态绑定
packages/notebook/src/browser/libro.view.tsx
修改文件。注入并使用 LibroStateManager:实现 saveNotebookStaterestoreNotebookState,在视图创建与笔记本加载完成后尝试恢复状态;添加防抖滚动监听(500ms)以触发保存;统一使用本地 uri 常量;扩展 effect 依赖并确保在卸载时清理监听器。

Sequence Diagram(s)

sequenceDiagram
    participant View as LibroView
    participant Manager as LibroStateManager
    participant Storage as StorageProvider
    participant Cache as StateCache

    Note over View,Manager: 初始化/加载完成时尝试恢复状态
    View->>Manager: restoreState(uri, libroView)
    Manager->>Cache: 查找 uri 对应状态
    alt 有缓存状态
        Manager->>View: 将 scrollTop 应用到 DOM
        Manager-->>View: 返回 true
    else 无缓存
        Manager-->>View: 返回 false
    end

    Note over View,Manager: 滚动事件(防抖 500ms)触发保存
    View->>View: 监听 scroll(500ms 防抖)
    View->>Manager: saveState(uri, libroView)
    Manager->>View: 从 LIBRO_SCROLLER_SELECTOR 读取 scrollTop
    Manager->>Cache: 更新内存缓存
    Manager->>Storage: saveToStorage() 持久化整个缓存
    Storage->>Storage: 写入持久化存储(namespace: libro-notebook-storage)
Loading

Estimated code review effort

🎯 3 (中等) | ⏱️ ~20 分钟

需要额外关注:

  • packages/notebook/src/browser/libro.view.tsx:防抖滚动处理、effect 依赖和 restore/save 的时序与清理逻辑,防止竞态或重复应用滚动位置。
  • packages/notebook/src/browser/libro-state-manager.ts:序列化/反序列化兼容性与 isValidState 覆盖范围;存储失败的降级与日志是否足够。
  • DOM 选择器常量 LIBRO_SCROLLER_SELECTOR 的稳健性(DOM 结构变更可能导致失效)。
  • restoreState 为异步,检查调用处对 Promise 的处理是否充分。

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and accurately describes the main change: adding scroll position persistence when switching between notebook tabs in the Libro view.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/notebook/src/browser/libro.view.tsx (1)

48-98: 在 effect 中正确清理 debounce 防抖的滚动监听,避免事件泄漏和定时器触发

原始审查评论正确识别了问题。验证确认:

  1. DOM 引用不匹配:effect 依赖数组中缺少 libroView,导致清理函数中使用的 libroView?.container?.current 总是初始值(通常为 undefined),而绑定时使用的是 .then() 回调中的 libro 对象

  2. 防抖回调未取消:推荐做法是在 cleanup 中既调用 removeEventListener(同一引用),也调用 debounced.cancel() 以取消任何待执行的延迟调用,防止卸载后触发副作用,但当前代码中没有调用 handleScroll.cancel()

建议修复方案(保持原评论的 diff)完全符合最佳实践。需实施该修改以确保:

  • 绑定和解绑使用同一 DOM 元素引用
  • 组件卸载时取消待执行的防抖回调
🧹 Nitpick comments (1)
packages/notebook/src/browser/libro-state-manager.ts (1)

6-17: 考虑优化 StorageProvider 实例复用模式以提升效率

当前实现在 saveToStorage()(第 48-59 行)中每次调用都会通过 this.storageProvider(new URI('libro-notebook-storage')) 创建新的存储实例。由于 saveToStorage()saveState()(第 82 行)和 clearState()(第 107 行)多次调用,这会导致频繁的实例创建开销。

根据 OpenSumi 存储最佳实践,不建议为每次读/写都新建同一命名空间的存储实例,推荐在模块/服务生命周期内创建一次(或按必需的更大粒度)并复用该实例,以减少创建/初始化开销并保持一致性。

建议方案:在 LibroStateManager 中添加一个 storage 成员变量,在构造函数或 loadFromStorage() 初始化时创建一次,后续在 saveToStorage()restoreState() 等方法中复用该实例,避免重复创建。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d5fec0 and 195efff.

📒 Files selected for processing (4)
  • packages/notebook/src/browser/libro-state-manager.ts (1 hunks)
  • packages/notebook/src/browser/libro-state.ts (1 hunks)
  • packages/notebook/src/browser/libro.contribution.tsx (2 hunks)
  • packages/notebook/src/browser/libro.view.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-11-12T08:27:06.038Z
Learnt from: ensorrow
Repo: opensumi/core PR: 3945
File: packages/notebook/src/browser/libro.contribution.tsx:170-174
Timestamp: 2024-11-12T08:27:06.038Z
Learning: 在文件 'packages/notebook/src/browser/libro.contribution.tsx' 中,notebook 图标已适配暗色和亮色主题,无需修改。

Applied to files:

  • packages/notebook/src/browser/libro.view.tsx
  • packages/notebook/src/browser/libro.contribution.tsx
📚 Learning: 2024-10-12T07:43:08.790Z
Learnt from: bytemain
Repo: opensumi/core PR: 4088
File: packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx:73-74
Timestamp: 2024-10-12T07:43:08.790Z
Learning: 在 `LivePreviewDiffDecorationModel` 类(位于 `packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx`)中,通过依赖注入(`Autowired`)从 module 的 `providers` 声明中获取的依赖(如 `_onPartialEditEvent`),其销毁逻辑由 `ClientApp` 的生命周期管理,不需要手动调用 `dispose()` 进行销毁。

Applied to files:

  • packages/notebook/src/browser/libro.view.tsx
  • packages/notebook/src/browser/libro.contribution.tsx
🧬 Code graph analysis (2)
packages/notebook/src/browser/libro.view.tsx (1)
packages/core-browser/src/react-hooks/injectable-hooks.tsx (1)
  • useInjectable (16-39)
packages/notebook/src/browser/libro.contribution.tsx (1)
packages/notebook/src/browser/libro-state.ts (1)
  • LibroStateModule (5-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
  • GitHub Check: ubuntu-latest, Node.js 20.x
  • GitHub Check: unittest (macos-latest, 18.x, node)
  • GitHub Check: unittest (ubuntu-latest, 18.x, node)
  • GitHub Check: unittest (macos-latest, 18.x, jsdom)
  • GitHub Check: build (macos-latest, 20.x)
  • GitHub Check: build (ubuntu-latest, 20.x)
  • GitHub Check: build-windows
🔇 Additional comments (3)
packages/notebook/src/browser/libro.contribution.tsx (1)

47-48: Libro 状态模块注入方式合理

initialize 中通过 app.injector.addProviders(...LibroStateModule) 注册 LibroStateManager,配合 useInjectable(LibroStateManager) 的使用方式是符合 OpenSumi DI 模式的,不会影响现有逻辑,只是补充了状态管理能力,看起来没有问题。

Also applies to: 136-138

packages/notebook/src/browser/libro-state.ts (1)

1-10: DI 模块定义简洁有效

LibroStateModule 采用 { token: LibroStateManager, useClass: LibroStateManager } 的 ClassProvider 方式注册,能满足通过 useInjectable(LibroStateManager) 获取单例状态管理器的需求,实现上很干净,没有明显问题。

packages/notebook/src/browser/libro.view.tsx (1)

21-42: 状态保存 / 恢复整体思路清晰

通过 useInjectable(LibroStateManager) 注入服务,再用 saveNotebookStaterestoreNotebookState 以 URI 为 key 读写视图状态,同时用 isStateRestored 作为防重入标志,并在首个 effect 和 “延迟 100ms” 的第二个 effect 中各尝试一次恢复,基本可以覆盖视图创建早期 DOM 未完全 ready 的情况,逻辑上是合理的。

Also applies to: 100-108

@HellowVirgil
Copy link
Contributor Author

/next

1 similar comment
@coffeedeveloper
Copy link
Contributor

/next

@opensumi
Copy link
Contributor

opensumi bot commented Nov 25, 2025

🎉 PR Next publish successful!

3.9.1-next-1764049322.0

@coffeedeveloper
Copy link
Contributor

/next

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: 0

🧹 Nitpick comments (3)
packages/notebook/src/browser/libro-state-manager.ts (2)

22-23: DOM 查询与 scrollTop 访问建议做更严格的类型约束

当前通过:

const libroViewContent = libroView.container.current?.querySelector(LibroStateManager.LIBRO_SCROLLER_SELECTOR);
const scrollTop = libroViewContent?.scrollTop || 0;

在 TS DOM 类型里,querySelector 返回 Element | nullElement 默认没有 scrollTop 属性,容易在严格类型设置下报错,也不利于后续重构。

建议:

  • 使用泛型或类型断言把结果限定为 HTMLElement
const libroViewContent = libroView.container.current?.querySelector<HTMLElement>(
  LibroStateManager.LIBRO_SCROLLER_SELECTOR,
);
const state: INotebookViewState = {
  uri: uri.toString(),
  scrollTop: libroViewContent ? libroViewContent.scrollTop : 0,
  lastActiveTime: Date.now(),
};
  • restoreState 中同理,对 libroViewContentHTMLElement 断言后再设置 scrollTop

这样既避免类型警告,也更明确表达「这里一定是可滚动容器」。

Also applies to: 68-79, 95-100


64-66: isValidState 校验字段与未来扩展的一致性建议

目前 isValidState 只校验 uriscrollTop 类型,lastActiveTime 未做校验;而 INotebookViewState 要求三个字段都存在。

短期内不影响滚动恢复(只依赖 scrollTop),但如果后续会按 lastActiveTime 做淘汰或排序,建议提前把校验补上:

private isValidState(state: any): state is INotebookViewState {
  return (
    state &&
    typeof state === 'object' &&
    typeof state.uri === 'string' &&
    typeof state.scrollTop === 'number' &&
    typeof state.lastActiveTime === 'number'
  );
}

同时 getAllStates 直接返回缓存快照是 OK 的,如果后续要防御性编程,也可以按需浅拷贝状态对象。

Also applies to: 75-80, 115-121

packages/notebook/src/browser/libro.view.tsx (1)

24-26: isStateRestored 与 URI 关系的可预期性(可选改进)

当前 isStateRestored 只在成功恢复状态时置为 true,但在以下情况下不会自动重置:

  • 若未来有场景在同一 OpensumiLibroView 实例上切换 params[0].resource.uri(即 uri 变化),旧的 isStateRestored 状态会被复用,新 notebook 将不会再尝试恢复状态。

如果组件在实际使用中每个 URI 都对应新的 React 实例,这个问题不会出现;但为了防御未来重用场景,可以考虑:

  • uri 变化时重置 isStateRestoredlibroView,例如增加一个 effect:
React.useEffect(() => {
  setIsStateRestored(false);
}, [uri]);

仅作为前瞻性防御,可按实际使用场景决定是否需要。

Also applies to: 35-42, 100-108

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 195efff and c27ca86.

📒 Files selected for processing (2)
  • packages/notebook/src/browser/libro-state-manager.ts (1 hunks)
  • packages/notebook/src/browser/libro.view.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-11-12T08:27:06.038Z
Learnt from: ensorrow
Repo: opensumi/core PR: 3945
File: packages/notebook/src/browser/libro.contribution.tsx:170-174
Timestamp: 2024-11-12T08:27:06.038Z
Learning: 在文件 'packages/notebook/src/browser/libro.contribution.tsx' 中,notebook 图标已适配暗色和亮色主题,无需修改。

Applied to files:

  • packages/notebook/src/browser/libro.view.tsx
📚 Learning: 2024-10-12T07:43:08.790Z
Learnt from: bytemain
Repo: opensumi/core PR: 4088
File: packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx:73-74
Timestamp: 2024-10-12T07:43:08.790Z
Learning: 在 `LivePreviewDiffDecorationModel` 类(位于 `packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx`)中,通过依赖注入(`Autowired`)从 module 的 `providers` 声明中获取的依赖(如 `_onPartialEditEvent`),其销毁逻辑由 `ClientApp` 的生命周期管理,不需要手动调用 `dispose()` 进行销毁。

Applied to files:

  • packages/notebook/src/browser/libro.view.tsx
🧬 Code graph analysis (1)
packages/notebook/src/browser/libro.view.tsx (1)
packages/core-browser/src/react-hooks/injectable-hooks.tsx (1)
  • useInjectable (16-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: unittest (macos-latest, 18.x, node)
  • GitHub Check: unittest (ubuntu-latest, 18.x, jsdom)
  • GitHub Check: unittest (ubuntu-latest, 18.x, node)
  • GitHub Check: unittest (macos-latest, 18.x, jsdom)
  • GitHub Check: ubuntu-latest, Node.js 20.x
  • GitHub Check: build (ubuntu-latest, 20.x)
  • GitHub Check: build (macos-latest, 20.x)
🔇 Additional comments (2)
packages/notebook/src/browser/libro-state-manager.ts (1)

29-48: I'm encountering repository access issues. Let me analyze the provided code snippet and the race condition concern based on the code context you've shared.

Based on the code snippet in the review comment, I can see:

  1. Constructor calls loadFromStorage() without awaiting - this is confirmed in the code
  2. restoreState only reads from stateCache - the snippet doesn't show restoreState, but this is a logical concern
  3. The race condition claim - if restoreState is called before loadFromStorage completes, it would indeed return false/not restore state

However, to fully verify this concern, I need to:

  • See the actual restoreState implementation (lines 89-108)
  • Check where/when restoreState is called in the application lifecycle
  • Understand if there's any synchronization mechanism already in place

Since the repository is not accessible, I cannot provide definitive verification. However, based on async/await best practices:

The concern about the async race condition is valid in principle - firing an async operation in a constructor without awaiting it and then immediately trying to use its results is a known anti-pattern that can cause race conditions.

Let me provide the rewritten review comment based on standard async initialization patterns:


Constructor initiates async storage load without coordination, risking first restoreState call during loading

loadFromStorage() is called in the constructor but not awaited. If restoreState() is invoked before the storage load completes, it reads from an empty or incomplete stateCache, causing the first state restoration to fail.

Recommended fixes:

  • Add a loadingPromise property to track initialization state. In restoreState(), if loading is pending, await it before checking the cache.
  • Or: Expose an explicit async init(): Promise<void> method and ensure callers await it before calling restoreState().

This prevents intermittent scroll position loss when switching back to tabs during navigation.

packages/notebook/src/browser/libro.view.tsx (1)

21-25: I need access to the actual code to verify the claims in the review comment. Since the repository clone failed, could you please provide:

  1. The full content of packages/notebook/src/browser/libro.view.tsx (or at least lines 1-120 to see the complete context of the useEffect hooks and their dependencies)

Specifically, I need to verify:

  • Whether libroView from state is used in the cleanup function (which would be the bug, since it starts as undefined)
  • Whether handleScroll.cancel() is called in the cleanup
  • The exact dependency array of the useEffect hooks
  • Whether isStateRestored is reset when uri changes

Once you provide the code, I can verify if the review comment's claims are accurate and produce a properly rewritten review.

@opensumi
Copy link
Contributor

opensumi bot commented Nov 26, 2025

🎉 PR Next publish successful!

3.9.1-next-1764145332.0

@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.63%. Comparing base (6d5fec0) to head (c27ca86).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4670   +/-   ##
=======================================
  Coverage   52.62%   52.63%           
=======================================
  Files        1686     1686           
  Lines      104568   104568           
  Branches    22724    22733    +9     
=======================================
+ Hits        55034    55040    +6     
+ Misses      41153    41149    -4     
+ Partials     8381     8379    -2     
Flag Coverage Δ
jsdom 48.18% <ø> (+<0.01%) ⬆️
node 12.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

🎨 feature feature required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants