mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-31 16:33:34 +08:00
fix(desktop): surface a plugin reload failure instead of reporting success
pluginStore applies a plugin change, reloads the runtime, and resolves either way -- a failed reload is recorded in `refreshWarning` and the action still returns its success message. Nothing reads that field, so "disabled the plugin but the runtime never reloaded" reached the user as a plain success toast, indistinguishable from a clean run. The mutation failing still throws and still surfaces, so this was not fully silent; the half where the change lands but the reload does not was. PluginDetail now reads `refreshWarning` after the call -- the store clears it when a mutation starts, so anything present belongs to that action -- and reports a warning naming the reload failure. Kept in the component rather than the store: no store in this codebase reaches into useUIStore, and adding the first such call to fix a toast is not the place to introduce that.
This commit is contained in:
parent
4404adf804
commit
6900f7f9f4
@ -661,4 +661,52 @@ describe('Settings > Plugins tab', () => {
|
||||
expect(screen.getByRole('button', { name: /codex-rescue/i })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: /gpt-5-4-prompting/i })).toBeDisabled()
|
||||
})
|
||||
|
||||
// The store records a failed post-change runtime reload in `refreshWarning`
|
||||
// and still resolves, so the action itself reads as a success. Without the
|
||||
// warning surfacing, "disabled the plugin but the runtime never reloaded"
|
||||
// looks identical to a clean success.
|
||||
it('warns when a plugin change succeeds but the runtime reload fails', async () => {
|
||||
usePluginStore.setState({
|
||||
selectedPlugin: {
|
||||
id: 'github@claude-plugins-official',
|
||||
name: 'github',
|
||||
marketplace: 'claude-plugins-official',
|
||||
scope: 'user',
|
||||
enabled: true,
|
||||
hasErrors: false,
|
||||
isBuiltin: false,
|
||||
description: 'GitHub integration',
|
||||
componentCounts: {
|
||||
commands: 0, agents: 0, skills: 0, hooks: 0, mcpServers: 0, lspServers: 0,
|
||||
},
|
||||
capabilities: {
|
||||
commands: [], agents: [], skills: [], hooks: [], mcpServers: [], lspServers: [],
|
||||
},
|
||||
commandEntries: [],
|
||||
agentEntries: [],
|
||||
hookEntries: [],
|
||||
skillEntries: [],
|
||||
mcpServerEntries: [],
|
||||
errors: [],
|
||||
},
|
||||
disablePlugin: async () => {
|
||||
usePluginStore.setState({ refreshWarning: 'runtime refresh failed' })
|
||||
return 'Plugin disabled'
|
||||
},
|
||||
})
|
||||
|
||||
render(<Settings />)
|
||||
switchToPluginsTab()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Disable' }))
|
||||
|
||||
await waitFor(() => {
|
||||
const toasts = useUIStore.getState().toasts
|
||||
expect(
|
||||
toasts.some(t => t.type === 'warning' && t.message.includes('runtime refresh failed')),
|
||||
`expected a warning toast, got ${JSON.stringify(toasts)}`,
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -68,7 +68,20 @@ export function PluginDetail() {
|
||||
setActionKey(key)
|
||||
try {
|
||||
const message = await fn()
|
||||
addToast({ type: 'success', message })
|
||||
// The store applies the change, then reloads the runtime, and resolves
|
||||
// either way -- a failed reload only lands in `refreshWarning`. Reporting
|
||||
// a plain success here would make "applied but never reloaded" look
|
||||
// identical to a clean run. Read it after the call: the store clears it
|
||||
// when the mutation starts, so anything present belongs to this action.
|
||||
const refreshWarning = usePluginStore.getState().refreshWarning
|
||||
if (refreshWarning) {
|
||||
addToast({
|
||||
type: 'warning',
|
||||
message: t('settings.plugins.reloadWarning', { message: refreshWarning }),
|
||||
})
|
||||
} else {
|
||||
addToast({ type: 'success', message })
|
||||
}
|
||||
} catch (err) {
|
||||
addToast({
|
||||
type: 'error',
|
||||
|
||||
@ -1098,6 +1098,7 @@ export const en = {
|
||||
'settings.plugins.applyHint': 'Enable, disable, and update changes become active after applying them to the current desktop runtime.',
|
||||
'settings.plugins.lastReload': 'Last apply: {enabled} active plugins, {skills} plugin skills, {errors} errors.',
|
||||
'settings.plugins.reloadToast': 'Applied plugin changes: {enabled} active plugins, {skills} skills, {errors} errors.',
|
||||
'settings.plugins.reloadWarning': 'Plugin change applied, but reloading the runtime failed: {message}',
|
||||
'settings.plugins.marketplacesTitle': 'Known marketplaces',
|
||||
'settings.plugins.marketplacesHint': 'Read-only summary of marketplace sources already configured for this machine.',
|
||||
'settings.plugins.marketplaceAutoUpdateOn': 'Auto-update on',
|
||||
|
||||
@ -1100,6 +1100,7 @@ export const jp: Record<TranslationKey, string> = {
|
||||
'settings.plugins.applyHint': '有効化、無効化、更新の変更は、現在のデスクトップランタイムに適用した後で有効になります。',
|
||||
'settings.plugins.lastReload': '前回の適用: アクティブなプラグイン {enabled} 個、プラグインスキル {skills} 個、エラー {errors} 件。',
|
||||
'settings.plugins.reloadToast': 'プラグインの変更を適用しました: アクティブなプラグイン {enabled} 個、スキル {skills} 個、エラー {errors} 件。',
|
||||
'settings.plugins.reloadWarning': 'プラグインの変更は適用されましたが、ランタイムの再読み込みに失敗しました: {message}',
|
||||
'settings.plugins.marketplacesTitle': '既知のマーケットプレイス',
|
||||
'settings.plugins.marketplacesHint': 'このマシンに既に設定済みのマーケットプレイスソースの読み取り専用の概要です。',
|
||||
'settings.plugins.marketplaceAutoUpdateOn': '自動更新オン',
|
||||
|
||||
@ -1100,6 +1100,7 @@ export const kr: Record<TranslationKey, string> = {
|
||||
'settings.plugins.applyHint': '사용, 사용 안 함, 업데이트 변경 사항은 현재 데스크톱 런타임에 적용한 후 적용됩니다.',
|
||||
'settings.plugins.lastReload': '마지막 적용: 활성 플러그인 {enabled}개, 플러그인 스킬 {skills}개, 오류 {errors}건.',
|
||||
'settings.plugins.reloadToast': '플러그인 변경 사항을 적용했습니다: 활성 플러그인 {enabled}개, 스킬 {skills}개, 오류 {errors}건.',
|
||||
'settings.plugins.reloadWarning': '플러그인 변경 사항은 적용되었지만 런타임 다시 불러오기에 실패했습니다: {message}',
|
||||
'settings.plugins.marketplacesTitle': '알려진 마켓플레이스',
|
||||
'settings.plugins.marketplacesHint': '이 컴퓨터에 이미 구성된 마켓플레이스 소스의 읽기 전용 요약입니다.',
|
||||
'settings.plugins.marketplaceAutoUpdateOn': '자동 업데이트 켜짐',
|
||||
|
||||
@ -1100,6 +1100,7 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'settings.plugins.applyHint': '啟用、禁用或更新後,需要把外掛變更重新應用到當前桌面端執行時。',
|
||||
'settings.plugins.lastReload': '最近一次應用:{enabled} 個活躍外掛,{skills} 個外掛技能,{errors} 個錯誤。',
|
||||
'settings.plugins.reloadToast': '外掛變更已應用:{enabled} 個活躍外掛,{skills} 個技能,{errors} 個錯誤。',
|
||||
'settings.plugins.reloadWarning': '外掛變更已應用,但執行階段重新載入失敗:{message}',
|
||||
'settings.plugins.marketplacesTitle': '已知外掛市場',
|
||||
'settings.plugins.marketplacesHint': '這裡只展示當前機器已經配置好的 marketplace 來源摘要。',
|
||||
'settings.plugins.marketplaceAutoUpdateOn': '自動更新開',
|
||||
|
||||
@ -1100,6 +1100,7 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'settings.plugins.applyHint': '启用、禁用或更新后,需要把插件变更重新应用到当前桌面端运行时。',
|
||||
'settings.plugins.lastReload': '最近一次应用:{enabled} 个活跃插件,{skills} 个插件技能,{errors} 个错误。',
|
||||
'settings.plugins.reloadToast': '插件变更已应用:{enabled} 个活跃插件,{skills} 个技能,{errors} 个错误。',
|
||||
'settings.plugins.reloadWarning': '插件变更已应用,但运行时重载失败:{message}',
|
||||
'settings.plugins.marketplacesTitle': '已知插件市场',
|
||||
'settings.plugins.marketplacesHint': '这里只展示当前机器已经配置好的 marketplace 来源摘要。',
|
||||
'settings.plugins.marketplaceAutoUpdateOn': '自动更新开',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user