feat(categories): improve category management and drag-to-assign#76
feat(categories): improve category management and drag-to-assign#76AmintaCCCP merged 2 commits intomainfrom
Conversation
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds hidden-default-category state and UI for hiding/restoring defaults, drag-and-drop repository→category assignment (locks category on drop), AI-assisted category resolution, propagation of category edits/deletes to repositories, and backend sync support for hidden-category state and category_locked. Changes
Sequence DiagramssequenceDiagram
participant User
participant Card as RepositoryCard
participant Sidebar as CategorySidebar
participant Store as AppStore
participant Backend
User->>Card: drag repository
Card->>Card: handleDragStart (set dataTransfer: application/x-gsm-repository-id)
User->>Sidebar: drop on category
Sidebar->>Sidebar: read repository id from dataTransfer
Sidebar->>Store: updateRepository(repoId, { custom_category, category_locked: true, last_edited })
Store->>Store: apply update to state
Sidebar->>Backend: forceSyncToBackend()
Backend->>Backend: persist changes
sequenceDiagram
participant AI
participant List as RepositoryList
participant Utils as categoryUtils.resolveCategoryAssignment
participant Store as AppStore
List->>AI: analyzeRepository(repo, categoryNames)
AI-->>List: tags
List->>Utils: resolveCategoryAssignment(repo, tags, allCategories)
Utils-->>List: resolvedCategoryName | undefined
List->>Store: updateRepository(..., custom_category: resolvedCategoryName)
Store->>Store: persist update (may set category_locked per repo)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/store/useAppStore.ts (1)
153-172:⚠️ Potential issue | 🟠 MajorNormalize hidden selections during rehydration.
If backup/restore or backend settings sync rehydrates
selectedCategoryto a hidden default id,getAllCategories()drops that category andsrc/components/RepositoryList.tsx:45-75will filter out every repository because the lookup fails. Coerce hidden default selections back to'all'while normalizing persisted state.Suggested fix
const repositories = Array.isArray(safePersisted.repositories) ? safePersisted.repositories : []; const releases = Array.isArray(safePersisted.releases) ? safePersisted.releases : []; + const hiddenDefaultCategoryIds = Array.isArray((safePersisted as any).hiddenDefaultCategoryIds) + ? (safePersisted as any).hiddenDefaultCategoryIds.filter((id: unknown): id is string => typeof id === 'string') + : []; + const selectedCategory = + typeof safePersisted.selectedCategory === 'string' && + hiddenDefaultCategoryIds.includes(safePersisted.selectedCategory) + ? 'all' + : safePersisted.selectedCategory || 'all'; const savedSortBy = safePersisted.searchFilters?.sortBy || 'stars'; const savedSortOrder = safePersisted.searchFilters?.sortOrder || 'desc'; @@ - hiddenDefaultCategoryIds: Array.isArray((safePersisted as any).hiddenDefaultCategoryIds) ? (safePersisted as any).hiddenDefaultCategoryIds.filter((id: unknown): id is string => typeof id === 'string') : [], + hiddenDefaultCategoryIds, @@ + selectedCategory, language: safePersisted.language || 'zh', isAuthenticated: !!(safePersisted.user && safePersisted.githubToken), };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/useAppStore.ts` around lines 153 - 172, The rehydrated state may contain selectedCategory pointing to a hidden default id, causing getAllCategories() to drop it and RepositoryList filtering to remove all repos; update the normalization in the useAppStore return to read hiddenDefaultCategoryIds (as currently normalized) and if safePersisted.selectedCategory is one of those hidden ids, coerce selectedCategory to 'all' (otherwise use safePersisted.selectedCategory or a default), ensuring you reference hiddenDefaultCategoryIds and selectedCategory when implementing the check and use proper type narrowing for the persisted values.
🤖 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/CategorySidebar.tsx`:
- Around line 128-133: The code is persisting the localized category label by
setting nextRepo.custom_category = category.name; instead persist a stable key
(e.g., category.id or category.key) so the category remains consistent across
locale changes; update the assignment in the nextRepo construction to use
category.id (or another stable identifier) for custom_category, keep
category_locked and last_edited as-is, and ensure any rendering logic reads the
stored id and resolves the localized display name when rendering rather than
storing the localized name on the repository object.
In `@src/components/SettingsPanel.tsx`:
- Around line 698-705: The Restore button only calls
showDefaultCategory(categoryId) so visibility is only changed in-memory; mirror
the hide path by also invoking the persistence/sync action used there (the same
function hideDefaultCategory or its underlying persistence helper) to push the
updated visibility to the store/backend. Concretely, after calling
showDefaultCategory(categoryId) call the existing persistence function used by
the hide flow (e.g., the push/save method that updates hiddenDefaultCategoryIds
or saveDefaultCategoryVisibility) with the categoryId and visible=true so the
change survives reloads and backend syncs.
In `@src/store/useAppStore.ts`:
- Around line 453-465: When removing a deleted custom category (in the
repositories and searchResults mappings that check repo.custom_category ===
targetCategory.name), also unset the stale manual lock by setting
repo.category_locked = false and update last_edited (e.g., to new
Date().toISOString()) for those same repo objects; ensure both the repositories
mapping and the searchResults mapping in the reducer/return block clear
custom_category, reset category_locked, and update last_edited for consistency.
In `@src/utils/categoryUtils.ts`:
- Around line 8-15: The resolver currently only preserves
repository.custom_category when repository.category_locked is true, causing
older manual categories (custom_category without the lock flag) to be dropped;
change the logic in the function that reads aiTags/normalizedTags so that if
repository.custom_category exists it is returned (or preserved) before falling
back to AI-derived categories—i.e., check repository.custom_category and return
it unconditionally (or at least when normalizedTags is empty) instead of only
when repository.category_locked is true, keeping the existing checks for
repository.category_locked and aiTags (references: repository.custom_category,
repository.category_locked, aiTags, normalizedTags).
---
Outside diff comments:
In `@src/store/useAppStore.ts`:
- Around line 153-172: The rehydrated state may contain selectedCategory
pointing to a hidden default id, causing getAllCategories() to drop it and
RepositoryList filtering to remove all repos; update the normalization in the
useAppStore return to read hiddenDefaultCategoryIds (as currently normalized)
and if safePersisted.selectedCategory is one of those hidden ids, coerce
selectedCategory to 'all' (otherwise use safePersisted.selectedCategory or a
default), ensuring you reference hiddenDefaultCategoryIds and selectedCategory
when implementing the check and use proper type narrowing for the persisted
values.
🪄 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: 5d9ffb84-4f61-4245-8e2e-91ad478755b0
📒 Files selected for processing (9)
src/components/CategorySidebar.tsxsrc/components/RepositoryCard.tsxsrc/components/RepositoryEditModal.tsxsrc/components/RepositoryList.tsxsrc/components/SettingsPanel.tsxsrc/services/autoSync.tssrc/store/useAppStore.tssrc/types/index.tssrc/utils/categoryUtils.ts
| const nextRepo = { | ||
| ...repository, | ||
| custom_category: category.name, | ||
| category_locked: true, | ||
| last_edited: new Date().toISOString(), | ||
| }; |
There was a problem hiding this comment.
Store a stable category key here, not the localized name.
Line 130 persists category.name, but built-in category names come from the current language. A repo dropped onto a default category in Chinese will stop matching after the user switches to English, because later comparisons use the new localized label. Persist the category id (or another stable key) and derive the display name when rendering.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/CategorySidebar.tsx` around lines 128 - 133, The code is
persisting the localized category label by setting nextRepo.custom_category =
category.name; instead persist a stable key (e.g., category.id or category.key)
so the category remains consistent across locale changes; update the assignment
in the nextRepo construction to use category.id (or another stable identifier)
for custom_category, keep category_locked and last_edited as-is, and ensure any
rendering logic reads the stored id and resolves the localized display name when
rendering rather than storing the localized name on the repository object.
src/components/SettingsPanel.tsx
Outdated
| {hiddenDefaultCategoryIds.map((categoryId) => ( | ||
| <button | ||
| key={categoryId} | ||
| onClick={() => showDefaultCategory(categoryId)} | ||
| className="px-3 py-1.5 rounded-full bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800 transition-colors text-sm" | ||
| > | ||
| {t('恢复', 'Restore')} {categoryId} | ||
| </button> |
There was a problem hiding this comment.
Restore actions are only local right now.
Line 701 only calls showDefaultCategory(categoryId). Unlike the hide path, this never pushes the updated visibility state, so a reload or backend pull can hide the category again on the next sync.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/SettingsPanel.tsx` around lines 698 - 705, The Restore button
only calls showDefaultCategory(categoryId) so visibility is only changed
in-memory; mirror the hide path by also invoking the persistence/sync action
used there (the same function hideDefaultCategory or its underlying persistence
helper) to push the updated visibility to the store/backend. Concretely, after
calling showDefaultCategory(categoryId) call the existing persistence function
used by the hide flow (e.g., the push/save method that updates
hiddenDefaultCategoryIds or saveDefaultCategoryVisibility) with the categoryId
and visible=true so the change survives reloads and backend syncs.
| if (repository.category_locked && repository.custom_category) { | ||
| return repository.custom_category; | ||
| } | ||
|
|
||
| const normalizedTags = Array.isArray(aiTags) ? aiTags.filter(Boolean) : []; | ||
| if (normalizedTags.length === 0) { | ||
| return repository.category_locked ? repository.custom_category : undefined; | ||
| } |
There was a problem hiding this comment.
Preserve older manual categories during the lock rollout.
This resolver only keeps custom_category when category_locked is already set. Any existing repo that has a manual category from older data but no lock flag will resolve to undefined here, and the new AI update path will write that back over the stored category on the next analysis.
Also applies to: 33-36
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/categoryUtils.ts` around lines 8 - 15, The resolver currently only
preserves repository.custom_category when repository.category_locked is true,
causing older manual categories (custom_category without the lock flag) to be
dropped; change the logic in the function that reads aiTags/normalizedTags so
that if repository.custom_category exists it is returned (or preserved) before
falling back to AI-derived categories—i.e., check repository.custom_category and
return it unconditionally (or at least when normalizedTags is empty) instead of
only when repository.category_locked is true, keeping the existing checks for
repository.category_locked and aiTags (references: repository.custom_category,
repository.category_locked, aiTags, normalizedTags).
Fixes #72
Summary
This PR improves category management with a focus on the left sidebar categories users actually interact with.
What changed
category_lockedBehavior details
Validation
npm run buildpassedSummary by CodeRabbit