mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
Desktop plugin details now route into the shared Skills, Agents, and MCP management surfaces instead of maintaining separate read-only drilldowns. This also extends the desktop/server skill aggregation so plugin-provided skills appear in the shared list, groups MCP entries by source, and preserves detail-view back navigation based on where the user entered the page. The implementation keeps plugin detail as the high-level capability hub while pushing real inspection into the existing management pages. Disabled plugins no longer expose false navigation paths into shared views, and the agent-browser regression script was expanded to exercise the new end-to-end flows. Constraint: Shared Agents data only includes enabled plugin agents, so disabled plugins cannot deep-link into agent detail Rejected: Keep duplicating full Skills/Agents/MCP detail inside Plugin detail | creates divergent UI flows and stale data paths Confidence: high Scope-risk: moderate Reversibility: clean Directive: If a detail view can be opened from multiple entry points, keep the return target in store state rather than hardcoding a single back destination Tested: desktop vitest for plugins/skills/agents/mcp; desktop tsc --noEmit; desktop vite build; server skills API test; agent-browser web regression on plugin->skill/mcp and plugin->agent back navigation Not-tested: packaged desktop app regression after rebuilding the Tauri bundle
65 lines
1.6 KiB
TypeScript
65 lines
1.6 KiB
TypeScript
import { create } from 'zustand'
|
|
import { skillsApi } from '../api/skills'
|
|
import type { SkillMeta, SkillDetail } from '../types/skill'
|
|
|
|
export type SkillDetailReturnTab = 'skills' | 'plugins'
|
|
|
|
type SkillStore = {
|
|
skills: SkillMeta[]
|
|
selectedSkill: SkillDetail | null
|
|
selectedSkillReturnTab: SkillDetailReturnTab
|
|
isLoading: boolean
|
|
isDetailLoading: boolean
|
|
error: string | null
|
|
|
|
fetchSkills: (cwd?: string) => Promise<void>
|
|
fetchSkillDetail: (
|
|
source: string,
|
|
name: string,
|
|
cwd?: string,
|
|
returnTab?: SkillDetailReturnTab,
|
|
) => Promise<void>
|
|
clearSelection: () => void
|
|
}
|
|
|
|
export const useSkillStore = create<SkillStore>((set) => ({
|
|
skills: [],
|
|
selectedSkill: null,
|
|
selectedSkillReturnTab: 'skills',
|
|
isLoading: false,
|
|
isDetailLoading: false,
|
|
error: null,
|
|
|
|
fetchSkills: async (cwd) => {
|
|
set({ isLoading: true, error: null })
|
|
try {
|
|
const { skills } = await skillsApi.list(cwd)
|
|
set({ skills, isLoading: false })
|
|
} catch (err) {
|
|
set({
|
|
error: err instanceof Error ? err.message : String(err),
|
|
isLoading: false,
|
|
})
|
|
}
|
|
},
|
|
|
|
fetchSkillDetail: async (source, name, cwd, returnTab = 'skills') => {
|
|
set({ isDetailLoading: true, error: null })
|
|
try {
|
|
const { detail } = await skillsApi.detail(source, name, cwd)
|
|
set({
|
|
selectedSkill: detail,
|
|
selectedSkillReturnTab: returnTab,
|
|
isDetailLoading: false,
|
|
})
|
|
} catch (err) {
|
|
set({
|
|
error: err instanceof Error ? err.message : String(err),
|
|
isDetailLoading: false,
|
|
})
|
|
}
|
|
},
|
|
|
|
clearSelection: () => set({ selectedSkill: null, selectedSkillReturnTab: 'skills' }),
|
|
}))
|