Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export const Header: React.FC = () => {
ai_tags: existing.ai_tags,
ai_platforms: existing.ai_platforms,
analyzed_at: existing.analyzed_at,
analysis_failed: existing.analysis_failed,
custom_description: existing.custom_description,
custom_tags: existing.custom_tags,
custom_category: existing.custom_category,
category_locked: existing.category_locked,
last_edited: existing.last_edited,
};
}
return newRepo;
Expand Down
48 changes: 43 additions & 5 deletions src/components/RepositoryEditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Save, X, Plus } from 'lucide-react';
import { Save, X, Plus, Lock, Unlock } from 'lucide-react';
import { Modal } from './Modal';
import { Repository } from '../types';
import { useAppStore, getAllCategories } from '../store/useAppStore';
Expand All @@ -20,7 +20,8 @@ export const RepositoryEditModal: React.FC<RepositoryEditModalProps> = ({
const [formData, setFormData] = useState({
description: '',
tags: [] as string[],
category: ''
category: '',
categoryLocked: false
});
const [newTag, setNewTag] = useState('');

Expand Down Expand Up @@ -74,21 +75,27 @@ export const RepositoryEditModal: React.FC<RepositoryEditModalProps> = ({
if (repository && isOpen) {
const currentCategory = getCurrentCategory(repository);
setFormData({
description: repository.custom_description || repository.description || '',
description: repository.custom_description || repository.ai_summary || repository.description || '',
tags: repository.custom_tags || repository.ai_tags || repository.topics || [],
category: currentCategory
category: currentCategory,
categoryLocked: !!repository.category_locked
});
}
}, [repository, isOpen]);

const handleSave = () => {
if (!repository) return;

const originalCategory = getCurrentCategory(repository);
const categoryChanged = formData.category !== originalCategory;

const updatedRepo = {
...repository,
custom_description: formData.description !== repository.description ? formData.description : undefined,
custom_tags: formData.tags.length > 0 ? formData.tags : undefined,
custom_category: formData.category ? formData.category : undefined,
// 手动修改分类时,默认自动锁定;用户仍可手动取消
category_locked: categoryChanged ? true : formData.categoryLocked,
Comment on lines +89 to +98
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.

⚠️ Potential issue | 🟠 Major

The save path can silently override the switch state.

When the category changes, Line 98 always persists category_locked: true, even if the user turned the toggle off in the same dialog. That leaves the control showing one state and saving another. Either force the toggle on/disable it as soon as the category changes, or make handleSave() respect the current toggle value.

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

In `@src/components/RepositoryEditModal.tsx` around lines 89 - 98, The save
currently forces category_locked to true when detecting a category change
(categoryChanged), which overrides the user's toggle; update the save logic in
handleSave so it respects the current toggle value instead of unconditionally
forcing true—replace the category_locked assignment in updatedRepo (currently
using categoryChanged ? true : formData.categoryLocked) with one that simply
uses formData.categoryLocked (or otherwise honor the toggle state), or
alternatively implement UI behavior that immediately forces/disables the toggle
when originalCategory vs formData.category changes so the saved value and
displayed toggle can't diverge.

Comment on lines +97 to +98
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if category_locked is consulted before AI analysis overwrites fields
rg -n -A5 "analyzeRepository" --type ts | head -50

Repository: AmintaCCCP/GithubStarsManager

Length of output: 2256


🏁 Script executed:

#!/bin/bash
# Find all places that set ai_summary, ai_tags to verify they respect the lock
rg -n "ai_summary.*=" --type ts

Repository: AmintaCCCP/GithubStarsManager

Length of output: 138


🏁 Script executed:

cat -n src/components/RepositoryList.tsx | sed -n '176,210p'

Repository: AmintaCCCP/GithubStarsManager

Length of output: 1407


🏁 Script executed:

# Also check if category_locked is referenced anywhere in RepositoryList.tsx
rg -n "category_locked" src/components/RepositoryList.tsx

Repository: AmintaCCCP/GithubStarsManager

Length of output: 55


🏁 Script executed:

# Search for all references to category_locked across the codebase
rg -n "category_locked" --type ts --type tsx

Repository: AmintaCCCP/GithubStarsManager

Length of output: 100


🏁 Script executed:

# Check the Repository type definition to understand category_locked field
rg -n "category_locked" --type ts --type tsx -B 2 -A 2

Repository: AmintaCCCP/GithubStarsManager

Length of output: 100


🏁 Script executed:

# Search for all references to category_locked across the codebase
rg -n "category_locked"

Repository: AmintaCCCP/GithubStarsManager

Length of output: 440


🏁 Script executed:

# Check the Repository type definition
fd ".*Repository.*" -e ts -e tsx | head -20

Repository: AmintaCCCP/GithubStarsManager

Length of output: 179


🏁 Script executed:

# Check the comment/context around category_locked in types
cat -n src/types/index.ts | sed -n '25,35p'

Repository: AmintaCCCP/GithubStarsManager

Length of output: 422


🏁 Script executed:

# Check Header.tsx to see how category_locked is used there
cat -n src/components/Header.tsx | sed -n '50,60p'

Repository: AmintaCCCP/GithubStarsManager

Length of output: 573


🏁 Script executed:

# Search for where category_locked is actually checked (used in conditionals)
rg -n "category_locked" -B 2 -A 2 --context 3

Repository: AmintaCCCP/GithubStarsManager

Length of output: 2117


🏁 Script executed:

# Check if there's any sync logic that respects category_locked
rg -n "sync" -A 5 -B 5 src/components/RepositoryList.tsx | head -60

Repository: AmintaCCCP/GithubStarsManager

Length of output: 1394


🏁 Script executed:

# Check the entire RepositoryList.tsx to see the broader context of the batch AI analysis
cat -n src/components/RepositoryList.tsx | sed -n '1,50p'

Repository: AmintaCCCP/GithubStarsManager

Length of output: 1949


🏁 Script executed:

# Check if category_locked is checked anywhere in sync operations
rg -n "sync" --context 5 src/components/Header.tsx | head -80

Repository: AmintaCCCP/GithubStarsManager

Length of output: 949


🏁 Script executed:

# Look for where the sync actually happens and if it checks category_locked
rg -n "updateRepository\|setRepositories" src/components/Header.tsx -B 3 -A 3 | head -50

Repository: AmintaCCCP/GithubStarsManager

Length of output: 55


🏁 Script executed:

# Look at the full handleSync function in Header.tsx
cat -n src/components/Header.tsx | sed -n '25,100p'

Repository: AmintaCCCP/GithubStarsManager

Length of output: 3166


🏁 Script executed:

# Check if category_locked is checked anywhere during repository updates or sync
rg -n "category_locked" -B 5 -A 5 src/components/Header.tsx

Repository: AmintaCCCP/GithubStarsManager

Length of output: 529


Add category_locked check before AI batch analysis updates.

The category_locked flag (defined in src/types/index.ts as preventing automatic category changes) is not consulted during AI batch analysis in RepositoryList.tsx. When analyzeRepository runs at lines 198-210, it overwrites ai_summary, ai_tags, and ai_platforms without checking if the category is locked. If category_locked should also prevent re-analysis of locked repositories, add a guard condition to skip or preserve fields for locked entries.

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

In `@src/components/RepositoryEditModal.tsx` around lines 97 - 98, The AI batch
analysis in analyzeRepository (RepositoryList.tsx) currently overwrites
ai_summary, ai_tags, and ai_platforms without checking the category_locked flag
set in RepositoryEditModal (category_locked), so update analyzeRepository to
read the repository's category_locked boolean and, if true, skip updating the
category-related outputs (or preserve existing ai_summary/ai_tags/ai_platforms)
instead of overwriting them; locate the analyzeRepository function and add a
guard that checks repo.category_locked before assigning to ai_summary, ai_tags,
ai_platforms to ensure locked repositories are not re-analyzed.

last_edited: new Date().toISOString()
};

Expand All @@ -100,7 +107,8 @@ export const RepositoryEditModal: React.FC<RepositoryEditModalProps> = ({
setFormData({
description: '',
tags: [],
category: ''
category: '',
categoryLocked: false
});
setNewTag('');
onClose();
Expand Down Expand Up @@ -202,6 +210,36 @@ export const RepositoryEditModal: React.FC<RepositoryEditModalProps> = ({
{t('当前分类:', 'Current category:')} {formData.category}
</p>
)}

<div className="mt-3 flex items-center justify-between rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-3 py-2">
<div className="flex items-center space-x-2">
{formData.categoryLocked ? (
<Lock className="w-4 h-4 text-amber-600 dark:text-amber-400" />
) : (
<Unlock className="w-4 h-4 text-gray-500 dark:text-gray-400" />
)}
<span className="text-sm text-gray-700 dark:text-gray-300">
{t('分类锁定', 'Category Lock')}
</span>
</div>
<label
className="inline-flex items-center cursor-pointer"
title={t(
'开启后,同步时不会自动修改该仓库分类。手动修改分类会自动开启此开关。',
'When enabled, sync will not auto-change this repository category. Manually changing category will enable this lock by default.'
)}
>
<input
type="checkbox"
checked={formData.categoryLocked}
onChange={(e) => setFormData(prev => ({ ...prev, categoryLocked: e.target.checked }))}
className="sr-only peer"
Comment on lines +225 to +236
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.

⚠️ Potential issue | 🟠 Major

Give the lock switch an accessible name.

The visible “Category Lock” text is outside the <label>, and the checkbox itself only has a tooltip. Screen readers will treat this as an unlabeled control. Please associate the text with the input via htmlFor/id or aria-labelledby/aria-label.

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

In `@src/components/RepositoryEditModal.tsx` around lines 225 - 236, The checkbox
input for the category lock (bound to formData.categoryLocked and updated by
setFormData) lacks an accessible name; add an id to the input (e.g.
category-lock-checkbox) and connect the existing <label> to it via htmlFor, or
alternatively add aria-labelledby/aria-label that references the visible
“Category Lock” text so screen readers properly announce the control; ensure the
id/label pair is unique and the onChange handler and className remain unchanged.

/>
<div className="relative w-10 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:bg-blue-600">
<div className="absolute top-[2px] left-[2px] bg-white border-gray-300 border rounded-full h-5 w-5 transition-transform peer-checked:translate-x-full"></div>
</div>
Comment on lines +238 to +240
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.

⚠️ Potential issue | 🟡 Minor

Toggle knob won't animate — peer-checked: on nested element.

Tailwind's peer-* variants use the CSS ~ sibling combinator, which only matches elements that are direct siblings of the .peer element. The inner knob div (line 239) is a child of a sibling, not a sibling itself, so peer-checked:translate-x-full will never apply. The track color change on line 238 will work (it's a sibling), but the knob won't slide.

♻️ Fix using `after:` pseudo-element pattern
-              <div className="relative w-10 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:bg-blue-600">
-                <div className="absolute top-[2px] left-[2px] bg-white border-gray-300 border rounded-full h-5 w-5 transition-transform peer-checked:translate-x-full"></div>
-              </div>
+              <div className="relative w-10 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500 dark:peer-focus:ring-blue-800 rounded-full dark:bg-gray-700 peer-checked:bg-blue-600 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-transform peer-checked:after:translate-x-full"></div>

This uses the after: pseudo-element for the knob, allowing peer-checked:after:translate-x-full to work correctly since the parent div is a sibling of the peer.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="relative w-10 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:bg-blue-600">
<div className="absolute top-[2px] left-[2px] bg-white border-gray-300 border rounded-full h-5 w-5 transition-transform peer-checked:translate-x-full"></div>
</div>
<div className="relative w-10 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500 dark:peer-focus:ring-blue-800 rounded-full dark:bg-gray-700 peer-checked:bg-blue-600 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-transform peer-checked:after:translate-x-full"></div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RepositoryEditModal.tsx` around lines 238 - 240, The inner
knob div is a child of the track div so the Tailwind
peer-checked:translate-x-full never matches; either move the knob element out so
it's a direct sibling of the element with class "peer" or convert the knob to an
after: pseudo-element on the track div and use
peer-checked:after:translate-x-full (update the track div's classes like
peer-checked:after:translate-x-full, after:... sizing and transition). Update
the JSX around the checkbox input/track so the element with "peer" and the
visual knob (now a sibling or an after:) are siblings and ensure transitions
remain (e.g., include transition-transform on the after/knob and set initial
translate/position via after: classes).

</label>
</div>
</div>

{/* Tags */}
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Repository {
custom_description?: string;
custom_tags?: string[];
custom_category?: string;
category_locked?: boolean; // 是否锁定分类(锁定后同步不自动改分类)
last_edited?: string;
}

Expand Down