mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-31 16:33:34 +08:00
fix(desktop): open installed skill details from the market overview
Clicking an installed skill in the Skills Market only wrote skillStore.selectedSkill, but the page renders details exclusively off marketStore.selectedId, so the click had no visible effect. Render the shared SkillDetail when an installed selection exists, mirroring the Settings -> Skills wiring: hidden (not unmounted) market home to preserve search/collapse state, and the same active-project context guard that drops stale details. Follow-up to #1098.
This commit is contained in:
parent
441979aa2d
commit
f82b596fbb
@ -10,7 +10,9 @@ vi.mock('../components/markdown/MarkdownRenderer', () => ({
|
||||
|
||||
import { useMarketStore } from '../stores/marketStore'
|
||||
import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { useSkillStore } from '../stores/skillStore'
|
||||
import type { NormalizedSkill, NormalizedSkillDetail } from '../types/market'
|
||||
import type { SkillDetail as LocalSkillDetail } from '../types/skill'
|
||||
import { Market } from './Market'
|
||||
|
||||
function makeSkill(overrides: Partial<NormalizedSkill> = {}): NormalizedSkill {
|
||||
@ -40,9 +42,32 @@ function makeDetail(overrides: Partial<NormalizedSkillDetail> = {}): NormalizedS
|
||||
}
|
||||
}
|
||||
|
||||
function makeLocalSkillDetail(overrides: Partial<LocalSkillDetail> = {}): LocalSkillDetail {
|
||||
return {
|
||||
meta: {
|
||||
name: 'local-demo',
|
||||
displayName: 'Local Demo',
|
||||
description: 'An installed skill',
|
||||
source: 'project',
|
||||
userInvocable: true,
|
||||
contentLength: 12,
|
||||
hasDirectory: true,
|
||||
},
|
||||
tree: [],
|
||||
files: [],
|
||||
skillRoot: '/tmp/local-demo',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
useSettingsStore.setState({ locale: 'en' })
|
||||
useSkillStore.setState({
|
||||
selectedSkill: null,
|
||||
selectedSkillContext: null,
|
||||
isDetailLoading: false,
|
||||
})
|
||||
const detail = makeDetail()
|
||||
useMarketStore.setState({
|
||||
items: [makeSkill()],
|
||||
@ -108,4 +133,41 @@ describe('Market', () => {
|
||||
'…/skills/demo/',
|
||||
)
|
||||
})
|
||||
|
||||
it('opens the installed skill detail selected from the overview and returns to the market home', async () => {
|
||||
render(<Market />)
|
||||
|
||||
act(() => {
|
||||
useSkillStore.setState({
|
||||
selectedSkill: makeLocalSkillDetail(),
|
||||
selectedSkillReturnTab: 'skills',
|
||||
selectedSkillContext: '',
|
||||
})
|
||||
})
|
||||
|
||||
expect(await screen.findByTestId('skill-detail-view')).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { name: 'Local Demo', level: 1 })).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Back to list' }))
|
||||
await waitFor(() => expect(screen.queryByTestId('skill-detail-view')).not.toBeInTheDocument())
|
||||
expect(screen.getByRole('button', { name: 'Demo Skill' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps the market detail view authoritative when a market skill is selected', async () => {
|
||||
render(<Market />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Demo Skill' }))
|
||||
expect(await screen.findByRole('heading', { name: 'Demo Skill', level: 1 })).toBeInTheDocument()
|
||||
|
||||
act(() => {
|
||||
useSkillStore.setState({
|
||||
selectedSkill: makeLocalSkillDetail(),
|
||||
selectedSkillReturnTab: 'skills',
|
||||
selectedSkillContext: '',
|
||||
})
|
||||
})
|
||||
|
||||
expect(screen.queryByRole('heading', { name: 'Local Demo' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { name: 'Demo Skill', level: 1 })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from '../i18n'
|
||||
import { useMarketStore } from '../stores/marketStore'
|
||||
import { useSessionStore } from '../stores/sessionStore'
|
||||
import { useSkillStore } from '../stores/skillStore'
|
||||
import { useUIStore } from '../stores/uiStore'
|
||||
import { InstallConfirmDialog } from '../components/market/InstallConfirmDialog'
|
||||
import { MarketHome } from '../components/market/MarketHome'
|
||||
import { MarketSkillDetail } from '../components/market/MarketSkillDetail'
|
||||
import { ConfirmDialog } from '../components/shared/ConfirmDialog'
|
||||
import { SkillDetail } from '../components/skills/SkillDetail'
|
||||
import type { NormalizedSkill } from '../types/market'
|
||||
|
||||
function focusSkillControl(id: string, preferred: 'action' | 'open' = 'action') {
|
||||
@ -28,6 +30,15 @@ export function Market() {
|
||||
const t = useTranslation()
|
||||
const selectedId = useMarketStore((s) => s.selectedId)
|
||||
const installingIds = useMarketStore((s) => s.installingIds)
|
||||
// InstalledSkillsOverview 点击已装技能只写 skillStore.selectedSkill,
|
||||
// 这里把它接成真正的详情视图,否则点击没有任何界面反馈
|
||||
const selectedInstalledSkill = useSkillStore((s) => s.selectedSkill)
|
||||
const selectedInstalledContext = useSkillStore((s) => s.selectedSkillContext)
|
||||
const isInstalledDetailLoading = useSkillStore((s) => s.isDetailLoading)
|
||||
const sessions = useSessionStore((s) => s.sessions)
|
||||
const activeSessionId = useSessionStore((s) => s.activeSessionId)
|
||||
const currentSkillContext = sessions.find((session) => session.id === activeSessionId)?.workDir ?? ''
|
||||
const showInstalledDetail = Boolean(selectedInstalledSkill) || isInstalledDetailLoading
|
||||
const [confirmInstall, setConfirmInstall] = useState<NormalizedSkill | null>(null)
|
||||
const [confirmUninstall, setConfirmUninstall] = useState<NormalizedSkill | null>(null)
|
||||
const lastOpenedIdRef = useRef<string | null>(null)
|
||||
@ -40,6 +51,17 @@ export function Market() {
|
||||
previousSelectedIdRef.current = selectedId
|
||||
}, [selectedId])
|
||||
|
||||
// 与 Settings → Skills 一致:活跃项目切换后,丢弃上一个项目上下文的详情
|
||||
useEffect(() => {
|
||||
if (
|
||||
(selectedInstalledSkill || isInstalledDetailLoading)
|
||||
&& selectedInstalledContext !== null
|
||||
&& selectedInstalledContext !== currentSkillContext
|
||||
) {
|
||||
useSkillStore.getState().clearSelection()
|
||||
}
|
||||
}, [currentSkillContext, isInstalledDetailLoading, selectedInstalledContext, selectedInstalledSkill])
|
||||
|
||||
const findSkill = (id: string): NormalizedSkill | null => {
|
||||
const state = useMarketStore.getState()
|
||||
if (state.detail?.id === id) return state.detail
|
||||
@ -108,13 +130,19 @@ export function Market() {
|
||||
{selectedId ? (
|
||||
<MarketSkillDetail onRequestInstall={requestInstall} onRequestUninstall={requestUninstall} />
|
||||
) : (
|
||||
<MarketHome
|
||||
onRequestInstall={requestInstall}
|
||||
onOpenSkill={(id) => {
|
||||
lastOpenedIdRef.current = id
|
||||
void useMarketStore.getState().openDetail(id)
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
{showInstalledDetail && <SkillDetail />}
|
||||
{/* hidden 而非卸载:保住 MarketHome/InstalledSkillsOverview 的搜索与折叠状态 */}
|
||||
<div hidden={showInstalledDetail} className="flex min-h-0 flex-1 flex-col">
|
||||
<MarketHome
|
||||
onRequestInstall={requestInstall}
|
||||
onOpenSkill={(id) => {
|
||||
lastOpenedIdRef.current = id
|
||||
void useMarketStore.getState().openDetail(id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<InstallConfirmDialog
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user