Skip to content

feat: 添加默认分类覆盖功能,支持修改/重置默认分类,并优化分类自定义状态判断#85

Merged
AmintaCCCP merged 6 commits intoAmintaCCCP:mainfrom
SummerRay160:main
Apr 19, 2026
Merged

feat: 添加默认分类覆盖功能,支持修改/重置默认分类,并优化分类自定义状态判断#85
AmintaCCCP merged 6 commits intoAmintaCCCP:mainfrom
SummerRay160:main

Conversation

@SummerRay160
Copy link
Copy Markdown
Contributor

@SummerRay160 SummerRay160 commented Apr 19, 2026

✨ 新功能

  • 默认分类覆盖:新增 defaultCategoryOverrides 状态,支持在界面上修改默认分类的名称、图标和关键词,并提供重置功能。
  • 更新分类编辑模态框、分类面板及分类获取函数,使其兼容默认分类覆盖逻辑。

🐛 修复

  • AI配置删除:在删除 AI 配置前增加 id 存在性检查,避免 undefined 导致的潜在错误。
  • 默认分类名称修改:修复修改默认分类名称时未能正确更新关联仓库的问题,优化分类名称变体的处理逻辑。

♻️ 重构

  • 分类自定义状态判断:统一各组件中分类是否“已自定义”的判断逻辑,确保仅当分类与 AI 或默认分类不一致时才标记为自定义。
  • 移除批量锁定分类时自动推断分类的功能,改为仅允许锁定已有自定义分类的仓库。

Summary by CodeRabbit

  • New Features

    • Override default category properties (name, icon, keywords) with per-category reset controls
    • Added keyword support for categories and keyword input when creating/editing
  • Improvements

    • Category editor shows modification state and disables Save until changes are made
    • Category lists, search, and bulk actions reflect default-category customizations consistently
    • Improved validation messages and accessibility hints in UI
  • Bug Fixes

    • Fixed notification timeout cleanup and API input validation for deletes

HappySummer added 2 commits April 19, 2026 15:20
添加 defaultCategoryOverrides 状态用于存储默认分类的修改,支持通过界面修改默认分类的名称、图标和关键词,并提供重置功能。同时更新相关组件以支持新的分类覆盖逻辑,包括分类编辑模态框、分类面板和分类获取函数。
防止在config.id为undefined时调用deleteAIConfig导致潜在错误
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 19, 2026

📝 Walkthrough
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding default category override functionality, supporting modification/reset of default categories, and optimizing category customization state detection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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

Caution

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

⚠️ Outside diff range comments (4)
src/components/settings/DataManagementPanel.tsx (2)

260-265: ⚠️ Potential issue | 🟡 Minor

Reset defaultCategoryOverrides in the delete-all state reset.

clearAllStorage() removes persisted data, but the in-memory reset should also include the new field to keep the app state consistent before reload or if reload is interrupted.

🐛 Proposed fix
         // 分类设置
         customCategories: [],
         hiddenDefaultCategoryIds: [],
+        defaultCategoryOverrides: {},
         categoryOrder: [],
         collapsedSidebarCategoryCount: 20,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/DataManagementPanel.tsx` around lines 260 - 265, The
in-memory state reset in DataManagementPanel's clearAllStorage (or the block
where customCategories, hiddenDefaultCategoryIds, categoryOrder,
collapsedSidebarCategoryCount are reset) is missing defaultCategoryOverrides;
update that reset to also set defaultCategoryOverrides to its initial
empty/default value so the app state matches the cleared persisted storage and
remains consistent if reload is interrupted.

45-56: ⚠️ Potential issue | 🟠 Major

Include default-category settings in the deletion count.

With the new default-category override feature, categorySettings can contain data even when customCategories.length === 0. In that case, the delete button is disabled, so users cannot clear defaultCategoryOverrides via the reset path added at Line 210.

🐛 Proposed fix
   const {
     user,
     repositories,
     releases,
     aiConfigs,
     webdavConfigs,
     customCategories,
+    hiddenDefaultCategoryIds,
+    defaultCategoryOverrides,
+    categoryOrder,
+    collapsedSidebarCategoryCount,
     setRepositories,
     setReleases,
     deleteCustomCategory,
   } = useAppStore();
+
+  const categorySettingsCount =
+    customCategories.length +
+    hiddenDefaultCategoryIds.length +
+    Object.keys(defaultCategoryOverrides).length +
+    categoryOrder.length +
+    (collapsedSidebarCategoryCount !== 20 ? 1 : 0);
     {
       key: 'categorySettings',
       label: t('自定义分类', 'Custom Categories'),
-      count: customCategories.length,
+      count: categorySettingsCount,
       icon: <FolderTree className="w-5 h-5" />,

Also applies to: 441-443, 522-525

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/DataManagementPanel.tsx` around lines 45 - 56, The
delete-button enablement logic only checks customCategories.length and ignores
categorySettings/defaultCategoryOverrides, so when customCategories is empty but
defaultCategoryOverrides exists the delete is disabled; update the checks that
decide whether the delete/reset button is enabled (references: customCategories,
categorySettings, defaultCategoryOverrides, deleteCustomCategory) to consider
defaultCategoryOverrides as part of the deletion count—e.g. compute a single
boolean or count that includes customCategories.length plus the number of keys
in categorySettings.defaultCategoryOverrides (or a truthy check on that object)
and use that combined value in the three places called out (the current block in
DataManagementPanel and the similar checks around the other lines) so the reset
path can clear defaultCategoryOverrides even when customCategories.length === 0.
src/components/RepositoryList.tsx (1)

755-765: ⚠️ Potential issue | 🟠 Major

Persist the inferred category when locking.

computeCustomCategory(inferredCategory, aiCat, defaultCat) can return undefined when the inferred category matches the AI/default category. This saves category_locked: true without a concrete custom_category, so future AI/default changes can still move the repo.

🐛 Proposed fix
                 if (inferredCategory) {
-                  const customCategoryValue = computeCustomCategory(inferredCategory, aiCat, defaultCat);
                   updateRepository({
                     ...repo,
-                    custom_category: customCategoryValue,
+                    custom_category: inferredCategory,
                     category_locked: true,
                     last_edited: new Date().toISOString()
                   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RepositoryList.tsx` around lines 755 - 765, The code locks a
repo using updateRepository but uses computeCustomCategory(...) which can return
undefined, causing category_locked: true without persisting a concrete
custom_category; change the payload so that custom_category is set to the
computed value or falls back to the inferredCategory (i.e., use
customCategoryValue ?? inferredCategory) before calling updateRepository; locate
the block where getAICategory, getDefaultCategory, computeCustomCategory,
inferredCategory, customCategoryValue and updateRepository are used and apply
this fallback.
src/store/useAppStore.ts (1)

774-827: ⚠️ Potential issue | 🔴 Critical

Critical: defaultCategoryOverrides is never persisted — overrides are lost on reload.

The field was added to PersistedAppState (line 141) and to the rehydration normalizer (lines 205-210), and the store state is initialized to {} (line 347), but partialize below does not include defaultCategoryOverrides. Zustand's persist middleware only writes the keys returned by partialize, so every mutation from updateDefaultCategory / resetDefaultCategory* lives in memory only and will be wiped on the next page load. The entire feature of "editing default categories" effectively doesn't survive a refresh.

Note the only repo-side effects that do get persisted are the rewrites of repo.custom_category, which means after a reload the user will see the renamed string on repositories but the default category label reverts — producing an inconsistent/orphaned state.

🔒 Proposed fix
         customCategories: state.customCategories,
         hiddenDefaultCategoryIds: state.hiddenDefaultCategoryIds,
+        defaultCategoryOverrides: state.defaultCategoryOverrides,
         categoryOrder: state.categoryOrder,
         collapsedSidebarCategoryCount: state.collapsedSidebarCategoryCount,

Also consider bumping version (line 772) and adding a migration step if you want to guarantee the field is normalized for users coming from v3.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/useAppStore.ts` around lines 774 - 827, The partialize function for
persist does not include defaultCategoryOverrides so that field (from
PersistedAppState) never gets written; to fix, add defaultCategoryOverrides to
the object returned by partialize in useAppStore (mirror how customCategories /
hiddenDefaultCategoryIds are persisted), ensuring you serialize the same shape
used in rehydration (e.g., convert Maps/Sets to plain objects/arrays if
defaultCategoryOverrides is a Map), and then bump the persist version and add a
migration step if needed so existing stores are normalized; check related
mutators updateDefaultCategory and resetDefaultCategory* to confirm they operate
on the persisted shape.
🧹 Nitpick comments (2)
src/components/settings/CategoryPanel.tsx (1)

226-228: Use override-aware categories in the drop snapshot too.

The rendered list is computed with defaultCategoryOverrides, but the drop handler re-derives categories without it. Keep this path consistent with the rest of the panel.

♻️ Proposed refactor
-    const currentCategories = getAllCategories(state.customCategories, state.language, state.hiddenDefaultCategoryIds);
+    const currentCategories = getAllCategories(
+      state.customCategories,
+      state.language,
+      state.hiddenDefaultCategoryIds,
+      state.defaultCategoryOverrides
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/CategoryPanel.tsx` around lines 226 - 228, The
drop-snapshot path recomputes categories without honoring
defaultCategoryOverrides, causing inconsistency with the rendered list; update
the snapshot logic to use the same override-aware call as the renderer by
passing state.defaultCategoryOverrides into getAllCategories (the same way
currentCategories is computed) before calling sortCategoriesByOrder, and ensure
the drop handler / snapshot uses useAppStore.getState(), getAllCategories(...,
state.language, state.hiddenDefaultCategoryIds, state.defaultCategoryOverrides)
and then sortCategoriesByOrder so both render and drop use the same
override-aware category list.
src/types/index.ts (1)

167-167: Narrow the override type to editable fields.

Partial<Category> allows persisted overrides for id, isCustom, and isHidden. Restricting the type to name, icon, and keywords keeps default-category overrides aligned with the feature surface.

♻️ Proposed refactor
 export interface Category {
   id: string;
   name: string;
   icon: string;
   keywords: string[];
   isCustom?: boolean;
   isHidden?: boolean;
 }
+
+export type DefaultCategoryOverride = Partial<Pick<Category, 'name' | 'icon' | 'keywords'>>;
-  defaultCategoryOverrides: Record<string, Partial<Category>>;
+  defaultCategoryOverrides: Record<string, DefaultCategoryOverride>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/index.ts` at line 167, The type for defaultCategoryOverrides is too
broad (Record<string, Partial<Category>>) and can let non-editable fields like
id/isCustom/isHidden be overridden; change it to only allow editable fields by
replacing the type with Record<string, Pick<Category, 'name' | 'icon' |
'keywords'>> (or define a new alias EditableCategoryFields = Pick<Category,
'name'|'icon'|'keywords'> and use Record<string, EditableCategoryFields>),
update any imports/usages of defaultCategoryOverrides to the new type, and run
TypeScript checks to fix any call sites that assumed other Category properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/CategoryEditModal.tsx`:
- Around line 861-867: The change-detection currently compares formData to the
stale prop category (used when the modal opened) so resets in the store don't
clear the "hasChanges" flag; update the logic to derive an effectiveCategory (a
live source of truth that reflects the store after reset actions) and use that
in the hasChanges computation instead of the original category prop; ensure
effectiveCategory is initialized from category when the modal opens and
updated/subscribed to after any reset actions so hasChanges compares
formData.name, formData.icon and formData.keywords against
effectiveCategory.name, effectiveCategory.icon and effectiveCategory.keywords
(joined) to correctly disable Save after resets (also apply same change to the
other hasChanges usage block around lines ~1028-1063).
- Around line 846-855: updateDefaultCategory currently merges all provided
fields into state.defaultCategoryOverrides[id] causing unchanged default fields
(name, icon, keywords) to become spurious overrides; change
updateDefaultCategory to compare incoming updates (name, icon, keywords) against
the original default values and only persist fields that differ, deleting keys
that match the default, and if after pruning the override object is empty remove
state.defaultCategoryOverrides[id] entirely (same pattern as
resetDefaultCategoryNameIcon). Locate updateDefaultCategory and
defaultCategoryOverrides in useAppStore and implement per-field
comparison/pruning logic (for name/icon/keywords) so CategoryEditModal's calls
no longer create false-positive modified flags.

In `@src/components/settings/AIConfigPanel.tsx`:
- Around line 637-640: The onClick handler currently guards deletion with if
(config.id) which silently no-ops for empty IDs; remove that truthiness check
and call deleteAIConfig(config.id) directly (reference: AIConfig.id and
deleteAIConfig) and add an explicit error/notification path when config.id is
missing or empty (e.g., show a toast or processLogger.error) so confirmed
deletions never fail silently.

In `@src/store/useAppStore.ts`:
- Around line 547-580: The updateDefaultCategory reducer currently merges
updates into defaultCategoryOverrides even when updates.name === "" which
creates an empty override name; modify updateDefaultCategory to
validate/normalize updates.name before merging (e.g., if updates.name is an
empty string, omit the name key or set it to undefined) so the spread that
builds nextOverrides does not write name: "" into
state.defaultCategoryOverrides; reference updateDefaultCategory and
defaultCategoryOverrides here and consider also touching getAllCategories only
as a fallback (but prefer validating in updateDefaultCategory).
- Around line 547-654: The handlers updateDefaultCategory, resetDefaultCategory
and resetDefaultCategoryNameIcon compare repo.custom_category only against the
original/default name (defaultCat.name) or override name, which fails when repos
were tagged using a translated/display name (e.g., English UI). Fix by
normalizing comparisons to include localized variants: for each handler, derive
both originalName and its translated version (use
getAllCategories/translateCategoryName or current language flag) and also derive
the override's translated form, then when mapping repositories and searchResults
match repo.custom_category against both the original and translated names (or
better yet switch storage to stable category IDs and compare id instead of
name); update references in those handlers (updateDefaultCategory,
resetDefaultCategory, resetDefaultCategoryNameIcon) and defaultCategoryOverrides
to ensure updates/removals handle both localized and original names.

---

Outside diff comments:
In `@src/components/RepositoryList.tsx`:
- Around line 755-765: The code locks a repo using updateRepository but uses
computeCustomCategory(...) which can return undefined, causing category_locked:
true without persisting a concrete custom_category; change the payload so that
custom_category is set to the computed value or falls back to the
inferredCategory (i.e., use customCategoryValue ?? inferredCategory) before
calling updateRepository; locate the block where getAICategory,
getDefaultCategory, computeCustomCategory, inferredCategory, customCategoryValue
and updateRepository are used and apply this fallback.

In `@src/components/settings/DataManagementPanel.tsx`:
- Around line 260-265: The in-memory state reset in DataManagementPanel's
clearAllStorage (or the block where customCategories, hiddenDefaultCategoryIds,
categoryOrder, collapsedSidebarCategoryCount are reset) is missing
defaultCategoryOverrides; update that reset to also set defaultCategoryOverrides
to its initial empty/default value so the app state matches the cleared
persisted storage and remains consistent if reload is interrupted.
- Around line 45-56: The delete-button enablement logic only checks
customCategories.length and ignores categorySettings/defaultCategoryOverrides,
so when customCategories is empty but defaultCategoryOverrides exists the delete
is disabled; update the checks that decide whether the delete/reset button is
enabled (references: customCategories, categorySettings,
defaultCategoryOverrides, deleteCustomCategory) to consider
defaultCategoryOverrides as part of the deletion count—e.g. compute a single
boolean or count that includes customCategories.length plus the number of keys
in categorySettings.defaultCategoryOverrides (or a truthy check on that object)
and use that combined value in the three places called out (the current block in
DataManagementPanel and the similar checks around the other lines) so the reset
path can clear defaultCategoryOverrides even when customCategories.length === 0.

In `@src/store/useAppStore.ts`:
- Around line 774-827: The partialize function for persist does not include
defaultCategoryOverrides so that field (from PersistedAppState) never gets
written; to fix, add defaultCategoryOverrides to the object returned by
partialize in useAppStore (mirror how customCategories /
hiddenDefaultCategoryIds are persisted), ensuring you serialize the same shape
used in rehydration (e.g., convert Maps/Sets to plain objects/arrays if
defaultCategoryOverrides is a Map), and then bump the persist version and add a
migration step if needed so existing stores are normalized; check related
mutators updateDefaultCategory and resetDefaultCategory* to confirm they operate
on the persisted shape.

---

Nitpick comments:
In `@src/components/settings/CategoryPanel.tsx`:
- Around line 226-228: The drop-snapshot path recomputes categories without
honoring defaultCategoryOverrides, causing inconsistency with the rendered list;
update the snapshot logic to use the same override-aware call as the renderer by
passing state.defaultCategoryOverrides into getAllCategories (the same way
currentCategories is computed) before calling sortCategoriesByOrder, and ensure
the drop handler / snapshot uses useAppStore.getState(), getAllCategories(...,
state.language, state.hiddenDefaultCategoryIds, state.defaultCategoryOverrides)
and then sortCategoriesByOrder so both render and drop use the same
override-aware category list.

In `@src/types/index.ts`:
- Line 167: The type for defaultCategoryOverrides is too broad (Record<string,
Partial<Category>>) and can let non-editable fields like id/isCustom/isHidden be
overridden; change it to only allow editable fields by replacing the type with
Record<string, Pick<Category, 'name' | 'icon' | 'keywords'>> (or define a new
alias EditableCategoryFields = Pick<Category, 'name'|'icon'|'keywords'> and use
Record<string, EditableCategoryFields>), update any imports/usages of
defaultCategoryOverrides to the new type, and run TypeScript checks to fix any
call sites that assumed other Category properties.
🪄 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: bc984096-5f98-417b-afb1-cc1ea1d6c708

📥 Commits

Reviewing files that changed from the base of the PR and between 7253842 and 9d33925.

📒 Files selected for processing (11)
  • src/components/BulkCategorizeModal.tsx
  • src/components/CategoryEditModal.tsx
  • src/components/CategorySidebar.tsx
  • src/components/RepositoryEditModal.tsx
  • src/components/RepositoryList.tsx
  • src/components/settings/AIConfigPanel.tsx
  • src/components/settings/CategoryPanel.tsx
  • src/components/settings/DataManagementPanel.tsx
  • src/store/useAppStore.ts
  • src/types/index.ts
  • test-sort-persistence.html
💤 Files with no reviewable changes (1)
  • test-sort-persistence.html

Comment thread src/components/CategoryEditModal.tsx
Comment thread src/components/CategoryEditModal.tsx
Comment thread src/components/settings/AIConfigPanel.tsx
Comment thread src/store/useAppStore.ts
HappySummer added 2 commits April 19, 2026 16:49
修复默认分类名称修改时未能正确更新关联仓库的问题,优化分类名称变体处理逻辑
统一各组件中分类自定义状态的判断逻辑,确保与AI/默认分类不一致时才显示为已自定义。移除批量锁定分类时自动推断分类的功能,仅允许锁定已有自定义分类的仓库。
@SummerRay160 SummerRay160 changed the title feat: 默认分类编辑功能 feat: 添加默认分类覆盖功能,支持修改/重置默认分类,并优化分类自定义状态判断 Apr 19, 2026
Copy link
Copy Markdown
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: 1

Caution

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

⚠️ Outside diff range comments (2)
src/components/RepositoryCard.tsx (1)

420-441: ⚠️ Potential issue | 🟡 Minor

Missing allCategories in displayContent useMemo deps — "Customized" badge goes stale.

isCategoryEdited now depends on getAICategory(repository, allCategories) and getDefaultCategory(repository, allCategories) (lines 421–422), but the useMemo dep array on line 441 doesn’t list allCategories. The React.memo comparator (lines 1017–1026) correctly triggers a re-render when category metadata changes, yet on re-render the memoized displayContent is returned from cache — so renaming/resetting a default category (or adjusting its keywords) won’t refresh the Customized indicator until one of the repo fields flips.

🔧 Suggested fix
-  }, [repository.custom_description, repository.description, repository.ai_summary, repository.analysis_failed, repository.analyzed_at, repository.custom_tags, repository.ai_tags, repository.topics, repository.custom_category, repository.category_locked, showAISummary, language]);
+  }, [repository.custom_description, repository.description, repository.ai_summary, repository.analysis_failed, repository.analyzed_at, repository.custom_tags, repository.ai_tags, repository.topics, repository.custom_category, repository.category_locked, showAISummary, language, allCategories]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RepositoryCard.tsx` around lines 420 - 441, The memo that
computes displayContent uses getAICategory(repository, allCategories) and
getDefaultCategory(repository, allCategories) to derive
isCategoryEdited/isCustomized but allCategories is missing from the useMemo
dependency array; update the useMemo that defines displayContent to include
allCategories so changes to category metadata invalidate the memo and refresh
isCategoryEdited/isCustomized (look for the useMemo block that computes
isCategoryEdited/isCustomized and calls getAICategory/getDefaultCategory).
src/components/SearchBar.tsx (1)

152-178: ⚠️ Potential issue | 🟠 Major

Missing dependencies: search results go stale after category edits.

applyFilters now closes over customCategories, hiddenDefaultCategoryIds, defaultCategoryOverrides, and language via getAllCategories(...) (line 245) and uses them for the isEdited filter (lines 309–314). But neither the search effect (line 164 deps) nor the real-time search effect (line 178 deps) includes any of these. If the user renames/resets a default category — a core flow in this PR — the current result set won’t re-filter and "已自定义/Customized" counts shown in the UI (which are correctly memoized via statusStats at line 110) will disagree with the actual displayed list until some listed dep changes.

🔧 Suggested fix
-  }, [searchFilters.languages, searchFilters.tags, searchFilters.platforms, searchFilters.isAnalyzed, searchFilters.isSubscribed, searchFilters.isEdited, searchFilters.isCategoryLocked, searchFilters.analysisFailed, searchFilters.minStars, searchFilters.maxStars, searchFilters.sortBy, searchFilters.sortOrder, searchFilters.query, repositories, releaseSubscriptions]);
+  }, [searchFilters.languages, searchFilters.tags, searchFilters.platforms, searchFilters.isAnalyzed, searchFilters.isSubscribed, searchFilters.isEdited, searchFilters.isCategoryLocked, searchFilters.analysisFailed, searchFilters.minStars, searchFilters.maxStars, searchFilters.sortBy, searchFilters.sortOrder, searchFilters.query, repositories, releaseSubscriptions, customCategories, hiddenDefaultCategoryIds, defaultCategoryOverrides, language]);
@@
-  }, [searchQuery, isRealTimeSearch, repositories]);
+  }, [searchQuery, isRealTimeSearch, repositories, customCategories, hiddenDefaultCategoryIds, defaultCategoryOverrides, language]);

Also consider memoizing allCategories once at the component level and passing it into applyFilters rather than recomputing it twice per call — that also eliminates the risk of future dep drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SearchBar.tsx` around lines 152 - 178, The search effects miss
dependencies used inside applyFilters (it now closes over getAllCategories which
reads customCategories, hiddenDefaultCategoryIds, defaultCategoryOverrides, and
language), causing stale results after category edits; update both useEffect
dependency arrays (the main search effect that calls performSearch and the
real-time effect that calls performRealTimeSearch/performBasicFilter) to include
customCategories, hiddenDefaultCategoryIds, defaultCategoryOverrides, and
language (or alternatively memoize getAllCategories/allCategories at component
level and pass that memoized allCategories into
applyFilters/performSearch/performRealTimeSearch so you only need to depend on
that single memoized value), and ensure applyFilters is invoked with the
memoized allCategories where used.
🧹 Nitpick comments (3)
server/src/routes/repositories.ts (1)

247-278: Optional: redundant isNaN check; consider scope alignment.

Same observation as in releases.ts: after /^\d+$/ passes, parseInt(idStr, 10) can never produce NaN, so only the id <= 0 branch (i.e. "0") is reachable. Either drop the redundant guard or fold both checks into /^[1-9]\d*$/.

Also, these server validation tweaks appear unrelated to the PR's stated objective (默认分类编辑功能 / default-category editing). Consider splitting unrelated server-side hardening into a separate PR for cleaner history and easier review.

♻️ Optional simplification
-    const idStr = req.params.id;
-    if (!/^\d+$/.test(idStr)) {
-      res.status(400).json({ error: 'Valid repository id required', code: 'INVALID_REPOSITORY_ID' });
-      return;
-    }
-    const id = parseInt(idStr, 10);
-
-    if (isNaN(id) || id <= 0) {
-      res.status(400).json({ error: 'Valid repository id required', code: 'INVALID_REPOSITORY_ID' });
-      return;
-    }
+    const idStr = req.params.id;
+    if (!/^[1-9]\d*$/.test(idStr)) {
+      res.status(400).json({ error: 'Valid repository id required', code: 'INVALID_REPOSITORY_ID' });
+      return;
+    }
+    const id = parseInt(idStr, 10);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/routes/repositories.ts` around lines 247 - 278, The numeric id
validation is redundant: after /^\d+$/.test(idStr) parseInt(idStr, 10) cannot be
NaN; replace the two-step check with a single regex that excludes zero (e.g.
/^[1-9]\d*$/) and then parse the id. Concretely, update the validation around
idStr/parseInt in repositories.ts to use /^[1-9]\d*$/ and drop the isNaN(id)
branch (keep the id <= 0 guard removed since the regex already prevents 0),
leaving the rest of the deletion transaction (deleteReleases, deleteRepo,
deleteAll, result handling) unchanged.
server/src/routes/releases.ts (1)

181-191: Optional: redundant isNaN check after regex.

Since /^\d+$/ guarantees idStr is a non-empty digit string, parseInt(idStr, 10) cannot return NaN. Only the id <= 0 branch (matching "0", "00", …) remains meaningful, so this block can be simplified or the regex tightened to /^[1-9]\d*$/ to fold both checks into the single regex.

♻️ Optional simplification
-    const idStr = req.params.id;
-    if (!/^\d+$/.test(idStr)) {
-      res.status(400).json({ error: 'Valid release id required', code: 'INVALID_RELEASE_ID' });
-      return;
-    }
-    const id = parseInt(idStr, 10);
-
-    if (isNaN(id) || id <= 0) {
-      res.status(400).json({ error: 'Valid release id required', code: 'INVALID_RELEASE_ID' });
-      return;
-    }
+    const idStr = req.params.id;
+    if (!/^[1-9]\d*$/.test(idStr)) {
+      res.status(400).json({ error: 'Valid release id required', code: 'INVALID_RELEASE_ID' });
+      return;
+    }
+    const id = parseInt(idStr, 10);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/routes/releases.ts` around lines 181 - 191, The validation is
redundant: after /^\d+$/.test(idStr) parseInt(idStr, 10) cannot be NaN; tighten
the check by changing the regex to /^[1-9]\d*$/ to reject zero-leading/zero
values (so "0" and "00" fail) and then remove the isNaN(id) check and its
branch; update the code that reads req.params.id (idStr) and conversion to id
(parseInt(idStr, 10)) accordingly so only the single regex+parseInt+id <= 0
check remains or, with the tightened regex, drop the id <= 0 check too and rely
on the regex before parseInt.
src/store/useAppStore.ts (1)

890-894: Tighten migration guard against null/array values.

typeof null === 'object' and typeof [] === 'object', so if a corrupted persisted payload stores null or an array for defaultCategoryOverrides, this guard silently passes and a bad shape survives migration. normalizePersistedState (lines 205–210) rescues it at rehydrate, so users aren’t broken — but it’s worth making the migration itself defensive:

-        if (state && typeof state.defaultCategoryOverrides !== 'object') {
+        const overrides = state?.defaultCategoryOverrides as unknown;
+        if (state && (overrides === null || typeof overrides !== 'object' || Array.isArray(overrides))) {
           console.log('Migrating from old version: initializing defaultCategoryOverrides');
           state.defaultCategoryOverrides = {};
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/useAppStore.ts` around lines 890 - 894, The migration guard
currently uses typeof to detect missing defaultCategoryOverrides which
misclassifies null and arrays as objects; update the check in the migration
block that touches state.defaultCategoryOverrides so it treats null, arrays, and
non-plain objects as invalid. Replace the condition with one that ensures
state.defaultCategoryOverrides is a plain object (e.g. check for truthiness,
!Array.isArray(state.defaultCategoryOverrides), and
Object.prototype.toString.call(state.defaultCategoryOverrides) === '[object
Object]'), and if that fails set state.defaultCategoryOverrides = {} (this keeps
behavior consistent with normalizePersistedState and functions like
normalizePersistedState).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store/useAppStore.ts`:
- Around line 560-591: The diffing logic treats the UI-displayed (localized)
category name as a real change because updates.name comes from CategoryEditModal
(pre-filled with the localized/display name) while originalName is the base
name; fix by treating the displayed/localized name as equivalent to originalName
when comparing and when cleaning mergedOverride: introduce/get the displayed
name for this category (e.g., via the same i18n/getAllCategories lookup used to
pre-fill the modal) and change the checks to consider equality if updates.name
=== originalName || updates.name === displayedName (and likewise when removing
mergedOverride.name check mergedOverride.name === originalName ||
mergedOverride.name === displayedName); update references in the name-diff block
(where updates.name is compared) and in the cleanup loop (where
mergedOverride.name is compared) to use the displayedName equivalence.

---

Outside diff comments:
In `@src/components/RepositoryCard.tsx`:
- Around line 420-441: The memo that computes displayContent uses
getAICategory(repository, allCategories) and getDefaultCategory(repository,
allCategories) to derive isCategoryEdited/isCustomized but allCategories is
missing from the useMemo dependency array; update the useMemo that defines
displayContent to include allCategories so changes to category metadata
invalidate the memo and refresh isCategoryEdited/isCustomized (look for the
useMemo block that computes isCategoryEdited/isCustomized and calls
getAICategory/getDefaultCategory).

In `@src/components/SearchBar.tsx`:
- Around line 152-178: The search effects miss dependencies used inside
applyFilters (it now closes over getAllCategories which reads customCategories,
hiddenDefaultCategoryIds, defaultCategoryOverrides, and language), causing stale
results after category edits; update both useEffect dependency arrays (the main
search effect that calls performSearch and the real-time effect that calls
performRealTimeSearch/performBasicFilter) to include customCategories,
hiddenDefaultCategoryIds, defaultCategoryOverrides, and language (or
alternatively memoize getAllCategories/allCategories at component level and pass
that memoized allCategories into
applyFilters/performSearch/performRealTimeSearch so you only need to depend on
that single memoized value), and ensure applyFilters is invoked with the
memoized allCategories where used.

---

Nitpick comments:
In `@server/src/routes/releases.ts`:
- Around line 181-191: The validation is redundant: after /^\d+$/.test(idStr)
parseInt(idStr, 10) cannot be NaN; tighten the check by changing the regex to
/^[1-9]\d*$/ to reject zero-leading/zero values (so "0" and "00" fail) and then
remove the isNaN(id) check and its branch; update the code that reads
req.params.id (idStr) and conversion to id (parseInt(idStr, 10)) accordingly so
only the single regex+parseInt+id <= 0 check remains or, with the tightened
regex, drop the id <= 0 check too and rely on the regex before parseInt.

In `@server/src/routes/repositories.ts`:
- Around line 247-278: The numeric id validation is redundant: after
/^\d+$/.test(idStr) parseInt(idStr, 10) cannot be NaN; replace the two-step
check with a single regex that excludes zero (e.g. /^[1-9]\d*$/) and then parse
the id. Concretely, update the validation around idStr/parseInt in
repositories.ts to use /^[1-9]\d*$/ and drop the isNaN(id) branch (keep the id
<= 0 guard removed since the regex already prevents 0), leaving the rest of the
deletion transaction (deleteReleases, deleteRepo, deleteAll, result handling)
unchanged.

In `@src/store/useAppStore.ts`:
- Around line 890-894: The migration guard currently uses typeof to detect
missing defaultCategoryOverrides which misclassifies null and arrays as objects;
update the check in the migration block that touches
state.defaultCategoryOverrides so it treats null, arrays, and non-plain objects
as invalid. Replace the condition with one that ensures
state.defaultCategoryOverrides is a plain object (e.g. check for truthiness,
!Array.isArray(state.defaultCategoryOverrides), and
Object.prototype.toString.call(state.defaultCategoryOverrides) === '[object
Object]'), and if that fails set state.defaultCategoryOverrides = {} (this keeps
behavior consistent with normalizePersistedState and functions like
normalizePersistedState).
🪄 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: 7c2c6394-eaf1-425a-9ea3-50cb74b53ef2

📥 Commits

Reviewing files that changed from the base of the PR and between 9d33925 and c2e7f65.

📒 Files selected for processing (12)
  • server/src/routes/releases.ts
  • server/src/routes/repositories.ts
  • server/src/types/api.ts
  • src/components/CategoryEditModal.tsx
  • src/components/Header.tsx
  • src/components/RepositoryCard.tsx
  • src/components/RepositoryEditModal.tsx
  • src/components/RepositoryList.tsx
  • src/components/SearchBar.tsx
  • src/components/settings/AIConfigPanel.tsx
  • src/components/settings/DataManagementPanel.tsx
  • src/store/useAppStore.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/Header.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/settings/AIConfigPanel.tsx
  • src/components/RepositoryList.tsx
  • src/components/CategoryEditModal.tsx
  • src/components/settings/DataManagementPanel.tsx

Comment thread src/store/useAppStore.ts Outdated
@SummerRay160 SummerRay160 marked this pull request as draft April 19, 2026 10:44
@SummerRay160 SummerRay160 marked this pull request as ready for review April 19, 2026 10:45
优化搜索组件中的分类过滤逻辑,将allCategories提取为memoized值避免重复计算。同时修复实时搜索和分类过滤的依赖关系,确保搜索功能正确响应分类变化。
Copy link
Copy Markdown
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store/useAppStore.ts`:
- Around line 891-895: The migration guard in useAppStore.ts incorrectly treats
null/arrays as valid because typeof null === 'object'; update the check that
initializes state.defaultCategoryOverrides so it only preserves plain objects:
e.g., replace the current typeof check with a predicate that rejects null and
arrays (for example: if (!state || state.defaultCategoryOverrides == null ||
Array.isArray(state.defaultCategoryOverrides) || typeof
state.defaultCategoryOverrides !== 'object') then set
state.defaultCategoryOverrides = {}), and ensure this logic is colocated with
the existing migration block that precedes normalizePersistedState so downstream
state.defaultCategoryOverrides[id] access is safe.
- Around line 547-622: The repo-rewrite is triggered by comparing updates.name
(newName) to the raw currentName (which uses originalName), causing EN
translated display names to differ and spuriously update repos. Fix
updateDefaultCategory by computing the effective current display name (e.g.,
const effectiveDisplayName = currentOverride?.name || displayedName) and use
that for the early-return check and for getCategoryNameVariants (replace uses of
currentName with effectiveDisplayName). This ensures icon/keywords-only edits in
the English locale won't fall through to the repositories/searchResults update
path and only real name changes will rewrite repo custom_category.
🪄 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: 6df757df-1c6d-4c16-ad03-82dcadb13000

📥 Commits

Reviewing files that changed from the base of the PR and between c2e7f65 and 65c1e41.

📒 Files selected for processing (3)
  • src/components/RepositoryCard.tsx
  • src/components/SearchBar.tsx
  • src/store/useAppStore.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/RepositoryCard.tsx
  • src/components/SearchBar.tsx

Comment thread src/store/useAppStore.ts
Comment thread src/store/useAppStore.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
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: 1

♻️ Duplicate comments (1)
src/store/useAppStore.ts (1)

893-897: ⚠️ Potential issue | 🟡 Minor

Reject null and arrays in the migration guard.

typeof null === 'object', and arrays also pass the current check. Tighten this guard so migration always leaves defaultCategoryOverrides as a record.

🛡️ Proposed fix
         // 从旧版本升级时,确保 defaultCategoryOverrides 字段存在
-        if (state && typeof state.defaultCategoryOverrides !== 'object') {
+        if (
+          state &&
+          (state.defaultCategoryOverrides === null ||
+            typeof state.defaultCategoryOverrides !== 'object' ||
+            Array.isArray(state.defaultCategoryOverrides))
+        ) {
           console.log('Migrating from old version: initializing defaultCategoryOverrides');
           state.defaultCategoryOverrides = {};
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/useAppStore.ts` around lines 893 - 897, The migration guard that
initializes state.defaultCategoryOverrides should reject null and arrays; update
the check around state/defaultCategoryOverrides so it only treats it as valid
when state exists and defaultCategoryOverrides is a plain object (e.g., typeof
state.defaultCategoryOverrides === 'object' && state.defaultCategoryOverrides
!== null && !Array.isArray(state.defaultCategoryOverrides')), and if that test
fails assign an empty object to state.defaultCategoryOverrides; locate the guard
around the state migration block that references state and
defaultCategoryOverrides and tighten the condition accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store/useAppStore.ts`:
- Around line 557-603: The merge logic currently ignores an explicitly submitted
updates.name when it equals original/displayed name, causing
existingOverride.name to persist; change the name filtering to treat an explicit
update as intentional (e.g., use a presence check like if ('name' in updates) to
populate filteredUpdates.name = updates.name) so mergedOverride receives the
submitted value, then let the existing cleanup loop (the block that inspects
mergedOverride.name/icon/keywords) remove the override when it matches
original/displayed/empty; apply the same presence-check approach to the similar
block referenced around the second occurrence to ensure explicit resets are
preserved for name resets.

---

Duplicate comments:
In `@src/store/useAppStore.ts`:
- Around line 893-897: The migration guard that initializes
state.defaultCategoryOverrides should reject null and arrays; update the check
around state/defaultCategoryOverrides so it only treats it as valid when state
exists and defaultCategoryOverrides is a plain object (e.g., typeof
state.defaultCategoryOverrides === 'object' && state.defaultCategoryOverrides
!== null && !Array.isArray(state.defaultCategoryOverrides')), and if that test
fails assign an empty object to state.defaultCategoryOverrides; locate the guard
around the state migration block that references state and
defaultCategoryOverrides and tighten the condition accordingly.
🪄 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: e357e04e-0194-4419-96bb-ec01f0fd1cd4

📥 Commits

Reviewing files that changed from the base of the PR and between 65c1e41 and ce3361c.

📒 Files selected for processing (1)
  • src/store/useAppStore.ts

Comment thread src/store/useAppStore.ts
@AmintaCCCP AmintaCCCP merged commit 75e4cab into AmintaCCCP:main Apr 19, 2026
5 checks passed
AmintaCCCP pushed a commit that referenced this pull request Apr 19, 2026
主要修改:
1. 解决与 #85 合并后的冲突(useAppStore.ts 版本号和 defaultCategoryOverrides)
2. 修复 SubscriptionDevCard.tsx 中标签渲染逻辑缺陷(空 ai_tags 数组不 fallback 到 topics)
3. 将顶部导航从订阅改为趋势(中英文)
4. 新增 Trending 频道(模拟近7天创建的 Star 最多的项目)
5. 优化订阅频道的中文本地化:
   - Most Stars -> 最多 Star
   - Most Forks -> 最多 Fork
   - Most DEV -> 热门开发者
   - Trending -> 热门趋势
AmintaCCCP added a commit that referenced this pull request Apr 19, 2026
* feat: 添加订阅功能 — GitHub 排行榜 Top10 订阅与 AI 分析

新增 3 个默认订阅频道:
- Most Stars:GitHub Star 数量最多的项目 Top 10
- Most Forks:GitHub Fork 数量最多的项目 Top 10
- Most DEV:GitHub 最受关注的开发者 Top 10 及其最热项目

主要变更:
- types/index.ts: 新增 SubscriptionChannel, SubscriptionRepo, SubscriptionDev 类型
- githubApi.ts: 新增 searchMostStars/searchMostForks/searchDailyDevs 搜索方法
- backendAdapter.ts: 新增 searchRepositories/searchUsers 后端代理方法
- server/proxy.ts: 新增 Search API 代理路由
- useAppStore.ts: 新增 subscription 状态管理、持久化(migrate v5)、channel ID 迁移(daily-dev→most-dev)
- Header.tsx: 三个导航断点(桌面/平板/手机)新增订阅入口(TrendingUp 图标)
- App.tsx: 新增 subscription 视图渲染
- SubscriptionView.tsx: 主视图含频道切换、手动刷新、批量 AI 分析
- SubscriptionSidebar.tsx: 左侧频道光标栏
- SubscriptionRepoCard.tsx: 排行仓库卡片(排名徽章+AI标签+平台图标)
- SubscriptionDevCard.tsx: 开发者排行卡片(头像+bio+最热项目)
- ErrorBoundary.tsx: 错误消息显示防御性处理

频道功能:手动刷新更新数据、批量 AI 分析(复用 AIAnalysisOptimizer)、
数据持久化到 IndexedDB、后端代理支持 Search API

* fix: 修复合并冲突、标签渲染问题,添加 Trending 频道,优化本地化

主要修改:
1. 解决与 #85 合并后的冲突(useAppStore.ts 版本号和 defaultCategoryOverrides)
2. 修复 SubscriptionDevCard.tsx 中标签渲染逻辑缺陷(空 ai_tags 数组不 fallback 到 topics)
3. 将顶部导航从订阅改为趋势(中英文)
4. 新增 Trending 频道(模拟近7天创建的 Star 最多的项目)
5. 优化订阅频道的中文本地化:
   - Most Stars -> 最多 Star
   - Most Forks -> 最多 Fork
   - Most DEV -> 热门开发者
   - Trending -> 热门趋势

---------

Co-authored-by: chan-yuu <2454101175@qq.com>
Co-authored-by: Clawd <clawd@openclaw.ai>
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.

2 participants