Skip to content

feat(resources): 添加资源更新回收站与单项、多选撤回 - #3647

Open
HadesScience wants to merge 19 commits into
PCL-Community:devfrom
HadesScience:codex/resource-update-recycle-v3
Open

HadesScience wants to merge 19 commits into
PCL-Community:devfrom
HadesScience:codex/resource-update-recycle-v3

Conversation

@HadesScience

@HadesScience HadesScience commented Oct 4, 2026 •

Copy link
Copy Markdown
简体中文 · 点击展开 / 收起

资源更新会将旧文件送入系统回收站,升级后无法在资源列表中直接恢复。本 PR 将因更新而替换的旧文件保存到实际游戏目录的 recycle,提供行内及多选“撤回”;普通删除仍使用系统回收站。

行为

  • 新实例安装后创建对应回收站目录,遵循版本隔离设置;已有实例按需创建,并保留嵌套模组、存档数据包等相对目录结构。
  • 下载成功后暂存在资源所在卷,再备份旧版并安装新版。撤回校验新版内容、恢复旧版并彻底删除新版;连续更新只撤回最后一次。
  • 启用与禁用副本使用准确路径分别索引。兼容已有记录及无歧义的状态切换,遇到同名兄弟副本或文件冲突时拒绝猜测。
  • 移动文件前持久化恢复日志;后台加载资源列表前恢复未完成的更新或撤回。发生冲突时保留文件及日志,避免覆盖未知内容。
  • 检查路径各级组件,拒绝符号链接和目录联接;修复游戏目录为卷根目录时的包含关系判断。
  • 批量更新逐项处理,失败后继续,并汇总成功数、失败数和失败文件名;清理本次更新的临时根目录。
  • 行内检查和撤回在后台执行,等待期间禁用该资源行。底栏显示可撤回数量,处理混合多选和失败汇总,支持窄窗口换行及过期异步结果校验。
  • 修复更新后复用 WPF 行内按钮的逻辑父级异常,以及数据包列表等待更新时使用错误路径集合的问题。

验证

  • Windows x64 / .NET SDK 10.0.401:现有 PCL.Core.Test 中 62 项回收站回归用例通过,包括独立副本、旧记录兼容、各移动阶段中断恢复、恢复冲突、目录联接、外部正常下载缓存及批量失败后继续。
  • 旧实现对照复现了记录冲突、根目录判断及目录联接问题,共 8 个失败用例;修复后这些用例通过。
  • 本地临时 WPF 工具 7 项检查通过:行内按钮复用和移除、两类多选底栏、900 / 600 / 360 像素布局,以及模拟文件操作阻塞时的界面响应和撤回完成状态。按审查要求,该工具不提交到仓库。
  • v4 Release x64 自包含单文件构建成功,0 个错误;现有编译警告仍存在。
  • 中断恢复采用各文件移动阶段的磁盘状态模拟,未执行真实断电测试。路径检查覆盖预先存在的链接,不提供对其他进程并发替换目录的原子防护。
  • PR 仅包含源码和现有测试项目中的测试,不包含构建产物、工具链、用户配置或运行日志。

审查状态

本轮逐项处理 7 条审查意见,并拆分为 GitHub 已验证签名的提交。PR 保持打开且可审查;维护者此前的 CHANGES_REQUESTED 不代表已撤销。本地验证结果与 GitHub CI 状态分别记录,未将等待批准的 CI 视为通过。

实现分支:HadesScience/PCL-CE:codex/resource-update-recycle-v3。

English · Click to expand / collapse

Resource updates send the previous file to the system Recycle Bin, leaving no direct way to restore it from the resource list. This PR keeps files replaced by updates in recycle under the active game directory and provides row and multi-selection Undo. Regular Delete continues to use the system Recycle Bin.

Behavior

  • Create matching recycle directories after installing a new instance, respecting version isolation. Existing instances create them on demand, preserving relative paths for nested mods and world datapacks.
  • After downloading, stage the new file on the resource volume, back up the old version, then install the new version. Undo verifies the installed content, restores the predecessor and permanently deletes the replacement. Only the latest consecutive update can be undone.
  • Index enabled and disabled copies by their exact paths. Support existing records and unambiguous state toggles; refuse ambiguous sibling matches and filename conflicts.
  • Persist a recovery journal before moving files. Recover interrupted updates and Undo before loading resource lists in the background. Preserve files and journals when conflicting contents prevent recovery.
  • Reject symlinks and junctions in path components, and fix containment checks for volume-root game directories.
  • Continue batch replacements after individual failures, report success/failure counts and failed filenames, and remove the update's temporary root directory.
  • Perform row availability checks and Undo in the background, disabling the resource row while waiting. Selection bars show eligible counts, handle mixed selections and failure summaries, wrap at narrow widths, and reject stale asynchronous results.
  • Fix WPF logical-parent errors when reusing row buttons and correct the path collection used by datapack lists while waiting for updates.

Validation

  • Windows x64 / .NET SDK 10.0.401: 62 recycle-bin regression cases passed in the existing PCL.Core.Test, including independent copies, legacy records, interrupted move stages, recovery conflicts, junctions, ordinary external download caches and continued batch processing after failure.
  • The previous implementation failed 8 comparison cases reproducing record collisions, root containment and junction traversal. These cases pass with the fixes.
  • 7 local WPF checks passed, covering row button reuse/removal, both selection bars, layouts at 900 / 600 / 360 pixels, dispatcher responsiveness during blocked file operations and completion state after Undo. The temporary tool is excluded from the repository as requested during review.
  • The v4 Release x64 self-contained single-file build completed with 0 errors; existing compiler warnings remain.
  • Interruption tests simulate on-disk states at each move stage; no physical power-loss test was performed. Path checks cover pre-existing links and are not atomic protection against another process concurrently replacing directories.
  • The PR contains source and tests in the existing test project, excluding build artifacts, toolchains, user configuration and runtime logs.

Review status

This round addresses the 7 review comments in separate GitHub-verified commits. The PR remains open and ready for review; the existing maintainer CHANGES_REQUESTED review has not been dismissed. Local validation and GitHub CI are tracked separately; CI awaiting approval is not counted as passing.

Implementation branch: HadesScience/PCL-CE:codex/resource-update-recycle-v3.

Sourcery 总结

实现可恢复的资源更新功能,加入游戏内回收站,并提供可靠的单项和多项撤销支持。

新功能:

  • 为资源更新中被替换的文件添加游戏内回收站,并支持对模组和数据包进行单项撤销和多选撤销。
  • 为新建和现有的 Minecraft 实例初始化隔离版本的回收目录,并保留资源的嵌套路径。
  • 提供后台撤销可用性检查、行操作、选择栏操作,以及本地化的结果报告。

错误修复:

  • 在加载资源列表前恢复中断的资源更新和撤销操作;发生冲突时保留文件和操作日志。
  • 防止不安全的路径遍历、通过符号链接或 junction 访问、启用/禁用状态匹配不明确、文件名冲突,以及替换或恢复时意外覆盖文件。
  • 修复数据包更新列表同步问题和 WPF 行按钮复用问题。

改进:

  • 独立处理批量更新,确保单项失败不会影响其他成功项,并汇总报告成功项、失败项和文件名。
  • 在资源所在卷暂存下载文件,并在替换或撤销前验证文件内容。
  • 处理完成后删除临时更新文件,并支持旧版撤销记录和状态切换。

测试:

  • 增加全面的回收站回归测试,涵盖替换和撤销、批量处理、中断移动后的恢复、冲突处理、旧版记录、路径安全和 junction 防护。
Original summary in English

Sourcery 摘要

实现可恢复的资源更新功能,包括游戏内回收站,以及可靠的单项和批量撤销支持。

新功能:

  • 为资源更新时被替换的文件添加游戏内回收站,并支持对模组和数据包进行单项撤销及多选撤销。
  • 为新建和现有的 Minecraft 实例初始化按版本隔离的回收站目录,同时保留资源的嵌套路径。

错误修复:

  • 加载资源列表前恢复中断的资源更新和撤销操作;发生冲突时保留文件和操作日志。
  • 防止不安全的路径遍历、访问符号链接或 junction、启用/禁用项匹配不明确,以及替换或还原时意外覆盖文件。
  • 修正数据包更新列表同步问题和 WPF 行按钮复用行为。

改进:

  • 独立处理批量更新和撤销操作;单项失败后继续处理,并报告成功、跳过和失败的资源。
  • 在资源所在卷上暂存下载内容,验证已安装和已备份的内容,并清理临时更新文件。
  • 在后台执行撤销可用性检查和撤销操作,同时保持选择控件响应灵敏,并防止使用过期结果。

测试:

  • 全面补充回收站回归测试,覆盖替换和撤销流程、旧版记录、中断操作恢复、冲突、批量失败、路径包含关系及 junction 防护。
Original summary in English

Summary by Sourcery

Implement recoverable resource updates with an in-game recycle bin and reliable single-item and bulk undo support.

New Features:

  • Add an in-game recycle bin for files replaced during resource updates, with single-item and multi-selection undo for mods and datapacks.
  • Initialize version-isolated recycle-bin directories for new and existing Minecraft instances while preserving nested resource paths.

Bug Fixes:

  • Recover interrupted resource update and undo operations before loading resource lists, preserving files and journals when conflicts occur.
  • Prevent unsafe path traversal, symlink or junction access, ambiguous enabled/disabled matches, and accidental overwrites during replacement or restoration.
  • Correct datapack update-list synchronization and WPF row-button reuse behavior.

Enhancements:

  • Process batch updates and undos independently, continue after individual failures, and report successful, skipped, and failed resources.
  • Stage downloads on the resource volume, validate installed and backed-up content, and clean up temporary update files.
  • Run undo availability checks and operations in the background with responsive selection controls and stale-result protection.

Tests:

  • Add comprehensive recycle-bin regression coverage for replacement and undo flows, legacy records, interrupted-operation recovery, conflicts, batch failures, path containment, and junction protection.

AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
AI-assisted implementation and verification using Codex (GPT-6). Submitted for human review.
@pcl-ce-automation pcl-ce-automation Bot added 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 size: XXL PR 大小评估:巨型 labels Oct 4, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

审查者指南

本 PR 引入了按实例游戏目录存储的资源更新回收站,以原子化文件移动、记录哈希校验和失败回滚支持单项及批量撤回;同时将该能力接入资源与存档数据包更新及 WPF 列表界面,并修复异步刷新、窄窗口布局和行按钮复用问题。

资源更新与撤回时序图

sequenceDiagram
    participant UI as Resource UI
    participant Undo as ResourceUpdateUndo
    participant Bin as ResourceUpdateRecycleBin
    participant Disk as Game directory

    UI->>Bin: Replace(originalPath, updatedPath, downloadedPath)
    Bin->>Disk: Write update record with UpdatedHash
    Bin->>Disk: Move originalPath to recycle backup
    Bin->>Disk: Move downloadedPath to updatedPath
    Bin-->>UI: Update complete

    UI->>Undo: CreateButton(item, gameDirectory, refresh)
    Undo->>Bin: CanUndo(item.Entry.path)
    Bin->>Disk: Validate record, backup, and updated hash
    Bin-->>Undo: Undo available
    UI->>Bin: Undo(updatedPath)
    Bin->>Disk: Move updated file to temporary path
    Bin->>Disk: Move backup to original path
    Bin->>Disk: Delete temporary updated file
    Bin->>Disk: Delete update record
    Bin-->>UI: Refresh resource list
Loading

批量资源撤回时序图

sequenceDiagram
    participant User as User
    participant UI as Selection bar
    participant Undo as ResourceUpdateUndo
    participant Bin as ResourceUpdateRecycleBin

    User->>UI: Click BtnSelectUndo
    UI->>Undo: UndoSelectedAsync(gameDirectory, paths)
    Undo->>Bin: UndoMany(paths)
    loop Each distinct selected path
        Bin->>Bin: CanUndo(path)
        alt Eligible backup and matching updated file
            Bin->>Bin: Undo(path)
            Bin-->>Undo: Add to RestoredPaths
        else No valid backup or record
            Bin-->>Undo: Add to SkippedPaths
        else Undo fails
            Bin-->>Undo: Add to Failures
        end
    end
    Undo-->>UI: Show restored, skipped, and failed counts
    UI->>UI: Clear selection and reload list
Loading

文件级变更

变更 详情 文件
添加资源更新专用的内置回收站和可验证的一次性撤回事务。
  • 按游戏目录保存旧资源及相对路径,支持嵌套目录、禁用文件和同名备份。
  • 记录更新前后路径及新版哈希,校验路径安全、内容一致性和最后一次更新关系。
  • 实现更新、撤回及批量撤回的暂存、回滚、冲突处理和失败隔离。
PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs
PCL.Core.Test/Minecraft/ResourceUpdateRecycleBinTest.cs
将资源与存档数据包更新流程接入内置回收站。
  • 下载完成后再替换旧文件,并为每次更新使用独立临时目录。
  • 保留系统回收站作为普通删除路径,增加目标文件名冲突和非法路径检查。
  • 修正数据包更新等待逻辑,使其使用存档数据包自身的更新路径集合。
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs
Plain Craft Launcher 2/Modules/Minecraft/ModLocalComp.cs
在资源列表和数据包列表中提供单项及多选撤回操作。
  • 仅对存在有效备份且新版未被修改的项目显示撤回按钮。
  • 在选择栏显示可撤回数量,批量操作跳过不符合条件的项目并汇总成功、跳过和失败结果。
  • 操作期间禁用相关控件,完成后清除选择并刷新列表。
Plain Craft Launcher 2/Pages/PageInstance/ResourceUpdateUndo.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml
PCL.Core/App/Localization/Languages/en-GB.xaml
PCL.Core/App/Localization/Languages/en-US.xaml
PCL.Core/App/Localization/Languages/zh-CN.xaml
PCL.Core/App/Localization/Languages/zh-TW.xaml
强化选择栏布局和异步刷新的一致性,并修复按钮复用导致的 WPF 父级异常。
  • 根据操作卡片实际高度调整列表底部边距,支持窄窗口换行。
  • 使用选择快照和刷新版本号丢弃过期异步计算结果。
  • 重建行按钮集合前清空旧面板子元素,避免复用控件保留逻辑父级。
Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml
在实例安装入口初始化内置回收站目录。
  • 新实例安装完成后按实际隔离游戏目录创建回收站及资源子目录。
  • 支持已有实例在资源更新时按需创建所需目录。
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.cs
PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs

可能相关的 Issue

  • #更新资源时自动把旧资源放回收站: PR 将资源更新前的旧文件移入内置回收站,并新增撤回功能,直接解决 Issue 需求。

提示和命令

与 Sourcery 交互

  • 触发新的审查: 在 pull request 中评论 @sourcery-ai review。
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 根据审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。你也可以回复审查评论并使用 @sourcery-ai issue,根据该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title,随时重新生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中你希望生成摘要的位置写入 @sourcery-ai summary,即可随时在该位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary,随时重新生成摘要。
  • 生成审查者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,此功能会很有用。
  • 忽略所有 Sourcery 审查: 在 pull request 中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。如果你希望从新的审查开始,这一功能尤其有用——别忘了评论 @sourcery-ai review 来触发新的审查!

自定义使用体验

访问你的控制面板以:

  • 启用或禁用审查功能,例如 Sourcery 生成的 pull request 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

本 PR 引入了按实例游戏目录存储的资源更新回收站,以原子化文件移动、记录哈希校验和失败回滚支持单项及批量撤回;同时将该能力接入资源与存档数据包更新及 WPF 列表界面,并修复异步刷新、窄窗口布局和行按钮复用问题。

Sequence diagram for resource update and undo

sequenceDiagram
    participant UI as Resource UI
    participant Undo as ResourceUpdateUndo
    participant Bin as ResourceUpdateRecycleBin
    participant Disk as Game directory

    UI->>Bin: Replace(originalPath, updatedPath, downloadedPath)
    Bin->>Disk: Write update record with UpdatedHash
    Bin->>Disk: Move originalPath to recycle backup
    Bin->>Disk: Move downloadedPath to updatedPath
    Bin-->>UI: Update complete

    UI->>Undo: CreateButton(item, gameDirectory, refresh)
    Undo->>Bin: CanUndo(item.Entry.path)
    Bin->>Disk: Validate record, backup, and updated hash
    Bin-->>Undo: Undo available
    UI->>Bin: Undo(updatedPath)
    Bin->>Disk: Move updated file to temporary path
    Bin->>Disk: Move backup to original path
    Bin->>Disk: Delete temporary updated file
    Bin->>Disk: Delete update record
    Bin-->>UI: Refresh resource list
Loading

Sequence diagram for bulk resource undo

sequenceDiagram
    participant User as User
    participant UI as Selection bar
    participant Undo as ResourceUpdateUndo
    participant Bin as ResourceUpdateRecycleBin

    User->>UI: Click BtnSelectUndo
    UI->>Undo: UndoSelectedAsync(gameDirectory, paths)
    Undo->>Bin: UndoMany(paths)
    loop Each distinct selected path
        Bin->>Bin: CanUndo(path)
        alt Eligible backup and matching updated file
            Bin->>Bin: Undo(path)
            Bin-->>Undo: Add to RestoredPaths
        else No valid backup or record
            Bin-->>Undo: Add to SkippedPaths
        else Undo fails
            Bin-->>Undo: Add to Failures
        end
    end
    Undo-->>UI: Show restored, skipped, and failed counts
    UI->>UI: Clear selection and reload list
Loading

File-Level Changes

Change Details Files
添加资源更新专用的内置回收站和可验证的一次性撤回事务。
  • 按游戏目录保存旧资源及相对路径,支持嵌套目录、禁用文件和同名备份。
  • 记录更新前后路径及新版哈希,校验路径安全、内容一致性和最后一次更新关系。
  • 实现更新、撤回及批量撤回的暂存、回滚、冲突处理和失败隔离。
PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs
PCL.Core.Test/Minecraft/ResourceUpdateRecycleBinTest.cs
将资源与存档数据包更新流程接入内置回收站。
  • 下载完成后再替换旧文件,并为每次更新使用独立临时目录。
  • 保留系统回收站作为普通删除路径,增加目标文件名冲突和非法路径检查。
  • 修正数据包更新等待逻辑,使其使用存档数据包自身的更新路径集合。
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs
Plain Craft Launcher 2/Modules/Minecraft/ModLocalComp.cs
在资源列表和数据包列表中提供单项及多选撤回操作。
  • 仅对存在有效备份且新版未被修改的项目显示撤回按钮。
  • 在选择栏显示可撤回数量,批量操作跳过不符合条件的项目并汇总成功、跳过和失败结果。
  • 操作期间禁用相关控件,完成后清除选择并刷新列表。
Plain Craft Launcher 2/Pages/PageInstance/ResourceUpdateUndo.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml
PCL.Core/App/Localization/Languages/en-GB.xaml
PCL.Core/App/Localization/Languages/en-US.xaml
PCL.Core/App/Localization/Languages/zh-CN.xaml
PCL.Core/App/Localization/Languages/zh-TW.xaml
强化选择栏布局和异步刷新的一致性,并修复按钮复用导致的 WPF 父级异常。
  • 根据操作卡片实际高度调整列表底部边距,支持窄窗口换行。
  • 使用选择快照和刷新版本号丢弃过期异步计算结果。
  • 重建行按钮集合前清空旧面板子元素,避免复用控件保留逻辑父级。
Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs
Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml
在实例安装入口初始化内置回收站目录。
  • 新实例安装完成后按实际隔离游戏目录创建回收站及资源子目录。
  • 支持已有实例在资源更新时按需创建所需目录。
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.cs
PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs

Possibly linked issues

  • #更新资源时自动把旧资源放回收站: PR将资源更新前的旧文件移入内置回收站,并新增撤回功能,直接解决Issue需求。

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Keep automated regression tests in the existing PCL.Core.Test project. The WPF verification harness remains local-only and is excluded from the PR. AI-assisted update using Codex (GPT-6).
@pcl-ce-automation pcl-ce-automation Bot added size: XL PR 大小评估:超大型 and removed size: XXL PR 大小评估:巨型 labels Oct 4, 2026
Rename the newly introduced private selection-margin and undo handlers to _PascalCase; update event bindings and call sites. Preserve existing file encodings. AI-assisted compliance review using Codex (GPT-6).
@pcl-ce-automation pcl-ce-automation Bot removed 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 size: XL PR 大小评估:超大型 labels Oct 4, 2026
@HadesScience HadesScience reopened this Oct 4, 2026
@pcl-ce-automation pcl-ce-automation Bot added 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 size: XL PR 大小评估:超大型 labels Oct 4, 2026
@HadesScience
HadesScience marked this pull request as ready for review October 8, 2026 16:08
@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 and removed 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 labels Oct 8, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

嘿——我发现了 7 个问题

AI Agent 提示词
请处理本次代码审查中的评论:

## 单独评论

### 评论 1
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="228" />
<code_context>
+    private static string _GetContainedPath(string directory, string path)
+    {
+        path = Path.GetFullPath(path);
+        if (!path.StartsWith(directory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
+            throw new ArgumentException("资源路径必须位于当前游戏目录内。", nameof(path));
+        return path;
</code_context>
<issue_to_address>
**文件可以逃逸出游戏目录**

当资源目录或回收站子目录是指向游戏目录外部的符号链接或 junction 时,`_GetContainedPath` 只检查规范化后的字符串前缀,因此 `Replace` 和 `Undo` 会跟随该链接,在预期目录之外移动或删除文件。

在允许文件操作前解析物理路径,或拒绝包含链接的路径组件。
</issue_to_address>

### 评论 2
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="76-80" />
<code_context>
+            try
+            {
+                File.WriteAllText(pendingRecordPath, JsonSerializer.Serialize(record));
+                File.Move(originalPath, backupPath);
+                backedUp = true;
+                File.Move(downloadedPath, updatedPath);
+                installed = true;
+                File.Move(pendingRecordPath, recordPath, true);
+            }
+            catch (Exception updateError)
</code_context>
<issue_to_address>
**中断的更新无法撤销**

当启动器或计算机在 `Replace` 或 `Undo` 执行文件移动期间终止时,异常处理程序无法在进程终止后回滚,并且启动时也不会恢复未完成的移动操作。原文件可能已经不存在,更新后的文件也可能仍以临时名称存在且没有可用记录,导致资源丢失或无法执行 Undo。

持久化可恢复的事务记录,并在启动时恢复未完成的 Replace 和 Undo 操作。

另见 `PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs:166-170`。
</issue_to_address>

### 评论 3
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="242-246" />
<code_context>
+
+    private static string _NormalizeResourcePath(string path)
+    {
+        while (path.EndsWith(".disabled", StringComparison.OrdinalIgnoreCase) ||
+               path.EndsWith(".old", StringComparison.OrdinalIgnoreCase))
+            path = path[..path.LastIndexOf('.')];
+        return path;
+    }
+
+    private static bool _SamePath(string left, string right) =>
</code_context>
<issue_to_address>
**启用和禁用状态的更新共用记录**

当同一资源的启用副本和禁用副本同时存在且都被更新时,`_GetRecordPath` 使用 `_NormalizeResourcePath`,该方法会移除 `.disabled` 和 `.old`,因此同时存在的启用和禁用副本会映射到同一个记录文件。后一次更新会覆盖前一次记录,而 `_TryReadRecord` 也会将两条路径视为等价;Undo 可能恢复兄弟副本的备份并丢弃所选文件,而不是恢复它自身的前一个版本。

应根据更新后的准确路径建立记录索引,并通过明确且无歧义的记录查找处理启用/禁用切换期间的文件名变化。

另见 `PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs:235-237`。
</issue_to_address>

### 评论 4
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="228-229" />
<code_context>
+    private static string _GetContainedPath(string directory, string path)
+    {
+        path = Path.GetFullPath(path);
+        if (!path.StartsWith(directory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
+            throw new ArgumentException("资源路径必须位于当前游戏目录内。", nameof(path));
+        return path;
+    }
</code_context>
<issue_to_address>
**根目录中的资源会被拒绝**

当配置的游戏目录是卷根目录或共享根目录时,`_GetContainedPath` 会在检查前缀前向根目录追加分隔符,从而产生重复的分隔符,使有效的资源路径无法匹配。`Replace` 会拒绝更新,而 `CanUndo` 找不到符合条件的资源。

构造和检查包含关系前缀时,应处理文件系统根目录的情况。

另见 `PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs:23`。
</issue_to_address>

### 评论 5
<location path="Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs" line_range="2191" />
<code_context>
-                        {
-                            ModBase.Log($"[Mod] 更新后的目标文件夹已被删除:{Entry.Value}", ModBase.LogLevel.Debug);
-                        }
+                        recycleBin.Replace(replacement.Original, replacement.Updated, replacement.Downloaded);
+                        finishedFileNames.Add(ModBase.GetFileNameFromPath(replacement.Updated));
                     }
</code_context>
<issue_to_address>
**失败的更新会留下混合批次**

当多资源更新中的某个替换失败时,`recycleBin.Replace` 抛出的异常会停止循环,因此在较早资源成功后,后续资源不会被安装。完成处理程序只会报告笼统的失败,用户最终得到一个混合批次,也没有逐资源的结果摘要。

按资源处理替换失败,继续处理整个批次,并报告哪些资源成功、哪些资源失败。

另见 `Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs:1355`。
</issue_to_address>

### 评论 6
<location path="Plain Craft Launcher 2/Pages/PageInstance/ResourceUpdateUndo.cs" line_range="32-33" />
<code_context>
+
+    public static MyIconButton? CreateButton(MyLocalCompItem item, string gameDirectory, Action refresh)
+    {
+        var recycleBin = new ResourceUpdateRecycleBin(gameDirectory);
+        if (item.Entry.IsFolder || !recycleBin.CanUndo(item.Entry.path)) return null;
+
+        var button = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/rotate-ccw", Tag = item };
</code_context>
<issue_to_address>
**大型资源会冻结界面**

当更新后的资源很大或存储设备速度较慢时,`CreateButton` 会调用 `CanUndo`,点击处理程序会调用 `Undo`;两者都会在 UI 线程上同步计算整个更新文件的哈希。因此,创建或刷新行以及点击 Undo 时,磁盘 I/O 都会阻塞界面。

将文件哈希计算移出 UI 线程,或避免在创建行和 Undo 过程中重复同步计算完整文件哈希。

另见 `Plain Craft Launcher 2/Pages/PageInstance/ResourceUpdateUndo.cs:45`、`Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs:689`、`Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs:333`。
</issue_to_address>

### 评论 7
<location path="Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs" line_range="2120" />
<code_context>
             var fileCopyList = new Dictionary<string, string>();
+            var replacements = new List<(string Original, string Downloaded, string Updated)>();
+            var recycleBin = new ResourceUpdateRecycleBin(PageInstanceLeft.McInstance.PathIndie);
+            var tempRoot = Path.Combine(ModBase.pathTemp, "DownloadedComp", Guid.NewGuid().ToString("N"));
             foreach (var Entry in modList)
             {
</code_context>
<issue_to_address>
**临时目录不断累积**

资源反复更新时,每次运行都会创建唯一的临时根目录和每个资源对应的目录,但完成清理只会删除已下载的文件。空目录会不断累积在 `DownloadedComp` 下。

下载文件清理完成后,删除本次更新的临时根目录。

另见 `Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs:1304-1305`。
</issue_to_address>

Sourcery 对开源项目免费——如果您喜欢我们的审查结果,请考虑分享 ✨
Original comment in English

Hey - I've found 7 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="228" />
<code_context>
+    private static string _GetContainedPath(string directory, string path)
+    {
+        path = Path.GetFullPath(path);
+        if (!path.StartsWith(directory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
+            throw new ArgumentException("资源路径必须位于当前游戏目录内。", nameof(path));
+        return path;
</code_context>
<issue_to_address>
**Files escape game directory**

When a resource or recycle subdirectory is a symlink or junction pointing outside the game directory, `_GetContainedPath` checks only the normalized string prefix, so `Replace` and `Undo` follow the link and move or delete files outside the intended directory.

Resolve physical paths or reject linked path components before allowing file operations.
</issue_to_address>

### Comment 2
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="76-80" />
<code_context>
+            try
+            {
+                File.WriteAllText(pendingRecordPath, JsonSerializer.Serialize(record));
+                File.Move(originalPath, backupPath);
+                backedUp = true;
+                File.Move(downloadedPath, updatedPath);
+                installed = true;
+                File.Move(pendingRecordPath, recordPath, true);
+            }
+            catch (Exception updateError)
</code_context>
<issue_to_address>
**Interrupted updates cannot be undone**

When the launcher or machine terminates during the file moves in `Replace` or `Undo`, the exception handlers cannot roll back after process termination, and startup does not recover incomplete moves. The original can be absent or the updated file can remain under a temporary name without a usable record, leaving the resource missing or unavailable to Undo.

Persist a recoverable transaction record and recover incomplete Replace and Undo operations at startup.

Also at `PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs:166-170`.
</issue_to_address>

### Comment 3
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="242-246" />
<code_context>
+
+    private static string _NormalizeResourcePath(string path)
+    {
+        while (path.EndsWith(".disabled", StringComparison.OrdinalIgnoreCase) ||
+               path.EndsWith(".old", StringComparison.OrdinalIgnoreCase))
+            path = path[..path.LastIndexOf('.')];
+        return path;
+    }
+
+    private static bool _SamePath(string left, string right) =>
</code_context>
<issue_to_address>
**Enabled and disabled updates share records**

When enabled and disabled copies of the same resource are both present and both are updated, `_GetRecordPath` uses `_NormalizeResourcePath`, which strips `.disabled` and `.old`, so simultaneously present enabled and disabled counterparts map to the same record file. The later update overwrites the earlier record, and `_TryReadRecord` also treats the paths as equivalent; Undo can restore the sibling's backup and discard the selected file instead of restoring its own predecessor.

Key records by the exact updated path and handle filename changes across enable/disable toggles with an explicit, unambiguous record lookup.

Also at `PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs:235-237`.
</issue_to_address>

### Comment 4
<location path="PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs" line_range="228-229" />
<code_context>
+    private static string _GetContainedPath(string directory, string path)
+    {
+        path = Path.GetFullPath(path);
+        if (!path.StartsWith(directory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
+            throw new ArgumentException("资源路径必须位于当前游戏目录内。", nameof(path));
+        return path;
+    }
</code_context>
<issue_to_address>
**Root-directory resources are rejected**

When the configured game directory is a volume or share root, `_GetContainedPath` appends a separator to the root before checking the prefix, producing a doubled separator that valid resource paths do not match. `Replace` rejects updates and `CanUndo` finds no eligible resources.

Handle filesystem roots when constructing and checking the containment prefix.

Also at `PCL.Core/Minecraft/ResourceUpdateRecycleBin.cs:23`.
</issue_to_address>

### Comment 5
<location path="Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs" line_range="2191" />
<code_context>
-                        {
-                            ModBase.Log($"[Mod] 更新后的目标文件夹已被删除:{Entry.Value}", ModBase.LogLevel.Debug);
-                        }
+                        recycleBin.Replace(replacement.Original, replacement.Updated, replacement.Downloaded);
+                        finishedFileNames.Add(ModBase.GetFileNameFromPath(replacement.Updated));
                     }
</code_context>
<issue_to_address>
**Failed updates leave mixed batches**

When a replacement in a multi-resource update fails, the exception from `recycleBin.Replace` stops the loop, so later resources are not installed after earlier ones have succeeded. The completion handler reports only a generic failure, leaving the user with a mixed batch and no per-resource summary.

Handle replacement failures per resource, continue processing the batch, and report which resources succeeded or failed.

Also at `Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs:1355`.
</issue_to_address>

### Comment 6
<location path="Plain Craft Launcher 2/Pages/PageInstance/ResourceUpdateUndo.cs" line_range="32-33" />
<code_context>
+
+    public static MyIconButton? CreateButton(MyLocalCompItem item, string gameDirectory, Action refresh)
+    {
+        var recycleBin = new ResourceUpdateRecycleBin(gameDirectory);
+        if (item.Entry.IsFolder || !recycleBin.CanUndo(item.Entry.path)) return null;
+
+        var button = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/rotate-ccw", Tag = item };
</code_context>
<issue_to_address>
**Large resources freeze the interface**

When an updated resource is large or stored on slow storage, `CreateButton` calls `CanUndo`, and the click handler calls `Undo`; both synchronously hash the entire updated file on the UI thread. Building or refreshing rows and clicking Undo therefore block the interface during disk I/O.

Move file hashing off the UI thread or avoid repeating synchronous full-file hashes during row creation and Undo.

Also at `Plain Craft Launcher 2/Pages/PageInstance/ResourceUpdateUndo.cs:45`, `Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs:689`, `Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs:333`.
</issue_to_address>

### Comment 7
<location path="Plain Craft Launcher 2/Pages/PageInstance/PageInstanceCompResource.xaml.cs" line_range="2120" />
<code_context>
             var fileCopyList = new Dictionary<string, string>();
+            var replacements = new List<(string Original, string Downloaded, string Updated)>();
+            var recycleBin = new ResourceUpdateRecycleBin(PageInstanceLeft.McInstance.PathIndie);
+            var tempRoot = Path.Combine(ModBase.pathTemp, "DownloadedComp", Guid.NewGuid().ToString("N"));
             foreach (var Entry in modList)
             {
</code_context>
<issue_to_address>
**Temporary directories accumulate**

When resources are updated repeatedly, each run creates a unique temp root and per-resource directories, but completion cleanup deletes only downloaded files. The empty directories accumulate under `DownloadedComp`.

Remove each update's temp root after its downloads are cleaned up.

Also at `Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSaves/PageInstanceSavesDatapack.xaml.cs:1304-1305`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@pcl-ce-automation pcl-ce-automation Bot added size: XXL PR 大小评估:巨型 and removed size: XL PR 大小评估:超大型 labels Oct 11, 2026
@HadesScience HadesScience changed the title feat(resources): 添加资源更新回收站与单项、多选撤回 [GPT-6] feat(resources): 添加资源更新回收站与单项、多选撤回 Oct 11, 2026
@whitecat346

Copy link
Copy Markdown
Member

@codex

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-25T05:54:36.105953Z 58ff31c New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@whitecat346

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 091c04b424

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1872 to +1874
var updatingPaths = loader.input.compType == CompType.DataPack
? PageInstanceSavesDatapack.updatingVersions
: PageInstanceCompResource.updatingVersions;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid dereferencing the absent datapack page after waiting

When a data-pack update is still running as PageInstanceSavesDatapack.UpdateResource calls ReloadDatapackFileList(true), GetRequireLoaderData supplies frm = null; routing data packs through their new updatingVersions list now makes this loader enter the wait branch, and once the update finishes it reaches the unconditional loader.input.frm.LoaderRun(...) at line 1901. That raises NullReferenceException and leaves the shared resource-list loader in the Failed state instead of showing the updated data packs, so the datapack branch needs to avoid using frm or invoke the datapack refresh path explicitly.

Useful? React with 👍 / 👎.

等待资源更新结束后,直接更新当前加载器的目录缓存,避免访问数据包页面未提供的资源页对象。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37b558f2b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Directory.CreateDirectory(resourcepacksFolder);
ModBase.Log("[Download] 自动创建资源包文件夹:" + resourcepacksFolder);
// 回收站与实际资源目录使用相同的版本隔离路径。
new ResourceUpdateRecycleBin(new McInstance(instanceFolder).PathIndie).InitializeFolders();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep recycle setup from failing combined installs

When a combined McInstall uses a game or instance path containing a junction or symlink, InitializeFolders() deterministically throws because ResourceUpdateRecycleBin._GetUnlinkedPath rejects every reparse-point ancestor. Unlike the best-effort call in McInstallState, this invocation is inside the blocking InstallGame task with no catch, so the combo reaches Failed and McInstallFailedClearFolder removes the newly installed instance even though the game installation itself succeeded; recycle-bin initialization should be logged and skipped instead of failing the install.

Useful? React with 👍 / 👎.


var stagedPath = Path.Combine(Path.GetDirectoryName(updatedPath)!,
".pcl-undo-" + Guid.NewGuid().ToString("N") + ".tmp");
record.OriginalHash = _HashFile(backupPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify the recorded backup hash before undoing

If a file under recycle is modified or corrupted after an update, CanUndo still reports it eligible because _TryReadRecord checks only backup existence and the current resource hash. This assignment then replaces the hash captured by Replace with the modified backup's hash, so Undo treats corrupt bytes as authoritative, deletes the valid new version, and restores them; compare the backup to the recorded OriginalHash before starting the transaction, only deriving it for genuinely legacy records.

Useful? React with 👍 / 👎.

组合安装中的回收站初始化采用与安装完成回调一致的异常处理,记录失败并继续,避免链接路径或目录创建失败触发实例清理。
撤回可用性和执行阶段校验更新时记录的旧版哈希,不再以备份当前内容覆盖校验值。仅为缺少哈希的历史记录补全恢复日志所需信息;损坏备份及无效哈希会拒绝撤回并保留新版、备份和记录。

新增 7 个回归用例:备份损坏(同名及改名更新)、缺少/空/null 旧版哈希兼容、无效及不匹配哈希。

验证:Linux .NET 10 临时 MSTest 项目直接编译仓库源码与现有测试文件,55 项可移植测试通过,包含新增 7 项;旧实现对照运行新增用例为 4 项失败、3 项通过。另对实际安装初始化代码片段执行 4 项独立检查通过。未运行 Windows 文件锁、junction、完整 WPF 构建或安装集成测试。
@MoYuan-CN

MoYuan-CN commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

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

Labels

size: XXL PR 大小评估:巨型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants