diff --git a/docs/agent-skill-sharing-implementation-checklist.md b/docs/agent-skill-sharing-implementation-checklist.md index 1d5e46370b1..3dc8f1ec50d 100644 --- a/docs/agent-skill-sharing-implementation-checklist.md +++ b/docs/agent-skill-sharing-implementation-checklist.md @@ -542,12 +542,13 @@ does not mean the surrounding phase is complete. installations. - [x] Detect an agent before creating its provider-specific configuration root. - [x] Avoid modifying roots for agents that consume `.agents/skills` directly. -- [ ] Keep loose-skill placement as the universal/default installation path. -- [ ] Use the portable root directly for compatible Cursor installations. -- [ ] Generate validated `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json` adapters only - for detected clients where native plugin installation is useful. -- [ ] Fall back to loose skills for unsupported plugin clients, including the current Codex IDE - extension, and retain bundle/version provenance for every installed skill. +- [x] Keep loose-skill placement as the universal/default installation path. +- [x] Do not add Cursor-specific placement in V1; keep the Agent Plugins-compatible root as the + portable archive shape while installing contained skills through canonical loose-skill paths. +- [x] Do not generate `.claude-plugin/plugin.json` or `.codex-plugin/plugin.json` adapters in V1; + native plugin installation remains a separately reviewed follow-up if it becomes useful. +- [x] Use loose skills for plugin clients, including the current Codex IDE extension, and retain + bundle/version provenance for every installed skill. ### Placement reconciliation @@ -886,7 +887,10 @@ does not mean the surrounding phase is complete. ### UX validation - [ ] Test keyboard shortcuts and labels with platform-aware macOS versus Windows/Linux behavior. -- [ ] Test loading, error, cancellation, partial, conflict, and stale-preview states. +- [x] Test loading, error, cancellation, partial, conflict, and stale-preview states. Renderer + coverage includes scan loading, unavailable links, upload/install cancellation, partial + provider results, keep-local conflicts, capability loss after preview, and selection + reconciliation after a changed discovery scan; package tests cover source drift. - [ ] Test screen-reader names, focus order, progress announcements, and destructive confirmations. - [ ] Test long names, release notes, paths, organization names, and localized copy. - [ ] Review trust and privacy wording with security and design. diff --git a/src/renderer/src/components/skills/SkillShareSelectionControls.tsx b/src/renderer/src/components/skills/SkillShareSelectionControls.tsx index f56fa7d618d..f49f31dd030 100644 --- a/src/renderer/src/components/skills/SkillShareSelectionControls.tsx +++ b/src/renderer/src/components/skills/SkillShareSelectionControls.tsx @@ -70,6 +70,24 @@ export function addShareableSkillResults( return next } +export function retainedShareableSkillSelection( + current: Set, + skills: readonly DiscoveredSkill[], + local: boolean +): Set { + const next = new Set() + const names = new Set() + for (const skill of skills) { + const name = shareSkillNameKey(skill) + if (!current.has(skill.id) || !isSkillShareEligible(skill, local) || names.has(name)) { + continue + } + next.add(skill.id) + names.add(name) + } + return next.size === current.size ? current : next +} + export function updatedSkillSelection( current: ReadonlySet, skillId: string, diff --git a/src/renderer/src/components/skills/SkillsPage.test.tsx b/src/renderer/src/components/skills/SkillsPage.test.tsx index 059cee3f3dc..8033cf0fe8f 100644 --- a/src/renderer/src/components/skills/SkillsPage.test.tsx +++ b/src/renderer/src/components/skills/SkillsPage.test.tsx @@ -252,4 +252,26 @@ describe('SkillsPage', () => { 'A skill with this name is already selected from another source.' ) }) + + it('drops stale selections when a refreshed scan no longer contains the skill', async () => { + const discover = vi + .fn() + .mockResolvedValueOnce(discoveryResult(['alpha', 'beta'])) + .mockResolvedValueOnce(discoveryResult(['beta'])) + Object.defineProperty(window, 'api', { + configurable: true, + value: { skills: skillsApi(discover), runtimeEnvironments: { call: vi.fn() } } + }) + + await renderPage() + await flushMicrotasks() + await act(async () => fireEvent.click(buttonNamed('Share skills'))) + await act(async () => fireEvent.click(selectionCheckbox('alpha'))) + expect(container?.textContent).toContain('1 selected') + + await act(async () => fireEvent.click(buttonNamed('Refresh'))) + await flushMicrotasks() + expect(container?.textContent).toContain('0 selected') + expect(renderedSkillNames()).toEqual(['beta']) + }) }) diff --git a/src/renderer/src/components/skills/SkillsPage.tsx b/src/renderer/src/components/skills/SkillsPage.tsx index 60c7b7e3cfa..a2d3a9b3fe5 100644 --- a/src/renderer/src/components/skills/SkillsPage.tsx +++ b/src/renderer/src/components/skills/SkillsPage.tsx @@ -29,6 +29,7 @@ import { SkillShareSelectionAction, SkillShareSelectionStatus, addShareableSkillResults, + retainedShareableSkillSelection, updatedSkillSelection } from './SkillShareSelectionControls' @@ -117,8 +118,12 @@ export default function SkillsPage(): React.JSX.Element { } try { const nextResult = await discoverSkillsForRuntimeTarget(runtimeTarget) + const local = runtimeTarget.kind === 'local' if (isCurrentScan()) { setResult(nextResult) + setSelectedSkillIds((current) => + retainedShareableSkillSelection(current, nextResult.skills, local) + ) } } catch (error) { console.error('Failed to discover skills:', error)