Skip to content

fix(backup): WebDAV 恢复后保留铃铛订阅、Watch 来源与已读状态#249

Merged
AmintaCCCP merged 2 commits into
mainfrom
fix/webdav-restore-release-subscriptions
Jul 17, 2026
Merged

fix(backup): WebDAV 恢复后保留铃铛订阅、Watch 来源与已读状态#249
AmintaCCCP merged 2 commits into
mainfrom
fix/webdav-restore-release-subscriptions

Conversation

@AmintaCCCP

@AmintaCCCP AmintaCCCP commented Jul 17, 2026

Copy link
Copy Markdown
Owner

问题

备份到 WebDAV → 恢复备份后,以下 release 相关数据全部丢失:

  • 仓库卡片上铃铛图标订阅的仓库(releaseSubscriptions
  • Release 来源设置中的「Watch 仓库」与「自定义订阅」(releaseSourceSettings.watchCustomReleaseRepos / customReleaseRepos
  • 发布已读标记(readReleases

根因

WebDAV 备份/恢复(src/components/settings/BackupPanel.tsx)自行手工拼装 JSON 负载,遗漏了这三项**仅存于前端 Zustand store(持久化到 IndexedDB)**的状态,且无对应服务端表;handleRestore 也从不回填它们。铃铛 UI 凭 releaseSubscriptions.has(repoId) 判定亮灭(RepositoryCard.tsx),恢复后该集合为 new Set() → 所有铃铛全灭,丢失静默。

repositories 里的每行 subscribed_to_releases 布尔列虽存活,但 UI 不读它(resolveReleaseSourcesreleaseSubscriptions.has(repo.id) 过滤),故不解决丢失。

平行的本地文件导出/导入 DataManagementPanel.tsx 本就处理正确,本次按其行为镜像到 WebDAV 链路。

改动(仅 BackupPanel.tsx

  • 备份:在 backupData 写入 releaseSubscriptionsreleaseSourceSettingsreadReleasesversion 1.01.1
  • 恢复setReleases 之后用 useAppStore.setState 回填订阅与已读 Set,用 setReleaseSourceSettings(... ?? null) 回填来源(setter 内部经 normalizeReleaseSourceSettingsnull/缺字段安全回退默认空)。
  • UI:「备份内容包括」文案新增一行「Release 订阅、来源与已读状态」。

向后兼容

旧版 1.0 备份无这些字段 —— Array.isArray 守卫 + setter 内置 normalizeReleaseSourceSettings(null) 回退默认空集合,恢复不报错、行为与升级前一致。

验证

  • tsc -p tsconfig.app.json:BackupPanel 零错误;eslint(BackupPanel):通过。
  • vitest:11 文件 / 110 测试全通过。
  • 端到端手测脚本见计划文件:订阅-备份-清空-恢复-核对铃铛与 Watch 列表;并用删掉 releaseSourceSettings 键的旧 1.0 备份回归向后兼容。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Backups now include release subscriptions, release source settings, and release read-status data.
    • The backup contents list has been updated to reflect the added release information.
  • Bug Fixes
    • Restoring older backups remains supported; release-related fields are applied only when present and in a compatible format.
    • Restore will safely skip applying release data if it can’t be read, with a warning logged.

…across WebDAV restore

WebDAV backup/restore (BackupPanel) hand-assembled its JSON payload and
omitted three release-related pieces of frontend-only Zustand state, and
handleRestore never read them back — so after a restore the bell-icon
subscriptions (releaseSubscriptions), the Watch/custom release source
lists (releaseSourceSettings.watchCustomReleaseRepos /
customReleaseRepos) and the read markers (readReleases) all reverted to
empty. These live only in the frontend (persisted to IndexedDB) with no
server-side table, and the bell UI keys off releaseSubscriptions.has(id),
so the loss was complete and silent.

Mirror the existing local-file export/import behavior in
DataManagementPanel: write the three fields into the backup blob
(version bumped 1.0 -> 1.1) and restore them via useAppStore.setState /
setReleaseSourceSettings. Legacy 1.0 backups without these fields restore
to empty via the setter's built-in normalizeReleaseSourceSettings(null)
fallback — no error, backward compatible.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9f6259f-d72f-4267-9386-ab4de803828c

📥 Commits

Reviewing files that changed from the base of the PR and between 51a298b and 9d2e221.

📒 Files selected for processing (1)
  • src/components/settings/BackupPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/settings/BackupPanel.tsx

📝 Walkthrough

Walkthrough

BackupPanel backup and restore now includes release subscriptions, source settings, and read-release state. The backup format version changes from 1.0 to 1.1, while restore remains compatible with backups lacking the new fields.

Changes

Release backup data

Layer / File(s) Summary
Release data in backup payload
src/components/settings/BackupPanel.tsx
Backup serialization adds release subscriptions, source settings, and read releases, updates the version to 1.1, and updates the included-content list.
Release state restoration
src/components/settings/BackupPanel.tsx
Restore conditionally loads release state into the store, converts subscription and read-release values to Set<number>, and logs warnings when restoration fails.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了 WebDAV 备份恢复后保留订阅、来源设置和已读状态这一主要变更。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/webdav-restore-release-subscriptions

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the backup and restore functionality in BackupPanel.tsx to include release subscriptions, release source settings, and read releases state, bumping the backup version to 1.1. The review feedback suggests adding defensive filtering to ensure only numbers are added to the releaseSubscriptions and readReleases sets, preventing type pollution from the untyped backup data. Additionally, it is recommended to avoid passing null to setReleaseSourceSettings to adhere to its expected type signature.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +160 to +162
if (Array.isArray(backupData.releaseSubscriptions)) {
useAppStore.setState({ releaseSubscriptions: new Set<number>(backupData.releaseSubscriptions) });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

为了防止备份文件损坏或被篡改导致 store 中的 Set<number> 混入非数字类型(由于 backupDataany 类型,直接传入 new Set 会绕过 TypeScript 的运行时类型检查),建议在恢复时进行防御性过滤,仅保留数字类型的 ID。这与 store 内部从本地存储恢复时的 normalizeNumberSet 行为保持一致。

          if (Array.isArray(backupData.releaseSubscriptions)) {
            const validIds = backupData.releaseSubscriptions.filter((id): id is number => typeof id === 'number');
            useAppStore.setState({ releaseSubscriptions: new Set<number>(validIds) });
          }

Comment thread src/components/settings/BackupPanel.tsx Outdated
Comment on lines +163 to +165
if (backupData.releaseSourceSettings !== undefined) {
setReleaseSourceSettings(backupData.releaseSourceSettings ?? null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Store 中的 setReleaseSourceSettings 动作在类型定义上接收的是非空的 ReleaseSourceSettings。虽然 backupData 作为 any 绕过了编译检查,但传入 null 依然违背了类型契约。建议通过简单的真值检查来避免传递 null,从而保证更好的类型安全。

Suggested change
if (backupData.releaseSourceSettings !== undefined) {
setReleaseSourceSettings(backupData.releaseSourceSettings ?? null);
}
if (backupData.releaseSourceSettings) {
setReleaseSourceSettings(backupData.releaseSourceSettings);
}

Comment on lines +166 to +168
if (Array.isArray(backupData.readReleases)) {
useAppStore.setState({ readReleases: new Set<number>(backupData.readReleases) });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

同样地,为了防止 readReleases 集合中混入非数字类型的 ID,建议在恢复时进行防御性过滤,确保类型安全。

          if (Array.isArray(backupData.readReleases)) {
            const validIds = backupData.readReleases.filter((id): id is number => typeof id === 'number');
            useAppStore.setState({ readReleases: new Set<number>(validIds) });
          }

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/settings/BackupPanel.tsx`:
- Around line 157-172: Validate backupData.releaseSubscriptions and
backupData.readReleases before constructing their Set instances by filtering
each array to retain only numeric elements, matching the store’s
normalizeNumberSet behavior. Update the corresponding useAppStore.setState calls
in the restore block while preserving the existing handling of
releaseSourceSettings and backward compatibility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 695624a4-c586-49b0-8871-87ae3847bfff

📥 Commits

Reviewing files that changed from the base of the PR and between 1dedeb5 and 51a298b.

📒 Files selected for processing (1)
  • src/components/settings/BackupPanel.tsx

Comment thread src/components/settings/BackupPanel.tsx
… type

Address review findings from gemini-code-assist and coderabbitai on PR #249:

- Filter releaseSubscriptions / readReleases to numeric IDs before
  constructing Set<number>, matching the store's private normalizeNumberSet
  behavior. Untrusted/corrupted backup JSON could otherwise pollute the
  sets with non-number types (since backupData is `any` from JSON.parse),
  silently breaking releaseSubscriptions.has(repoId) lookups.
- Pass a truthy (non-null) releaseSourceSettings to setReleaseSourceSettings
  instead of `?? null`, honoring the setter's non-null ReleaseSourceSettings
  type contract. Truthy guard keeps 1.0 backward compat (missing field ->
  skip -> store retains default).

Co-Authored-By: Claude <noreply@anthropic.com>
@AmintaCCCP
AmintaCCCP merged commit bf01136 into main Jul 17, 2026
5 checks passed
@AmintaCCCP
AmintaCCCP deleted the fix/webdav-restore-release-subscriptions branch July 17, 2026 16:36
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.

1 participant