fix(plugins): retain independent row controls during query suspension

This commit is contained in:
creatixchu committed 2026-10-20 20:17:53 +08:00
1 parent 817d4ec740
commit f8912d267f
3 files changed
+16 -10

No files matched your search

@@ -45,7 +45,7 @@ import { BundleDetailController, type BundleDetailsState } from './detail-store.
import type { LocaleSnapshot } from '@deepseek-ai/dsh-client-locale/client'
/**
* Read whether a bundle or one of its rows has a pending management action.
* Read whether a bundle has a pending management action.
* @param state Current manager snapshot.
* @param packageName Bundle whose controls and observers must wait.
* @returns Whether the bundle's management operation is still pending.
@@ -53,8 +53,7 @@ import type { LocaleSnapshot } from '@deepseek-ai/dsh-client-locale/client'
export function bundleBusy(state: PluginManagerState, packageName: string): boolean {
return state.busy.includes(packageName) || state.update?.packageName === packageName
|| (state.install.subject?.name === packageName && (isInstallPending(state.install.phase) || state.install.phase === 'checking'))
|| (state.packages.find(pkg => pkg.name === packageName)?.rows.some(row =>
row.entryId !== undefined && state.busy.includes(rowKey(row.entryId))) ?? false)
}
/** The action a failed notice names. */
@@ -690,7 +689,14 @@ export class PluginManagerController {
* @returns A read-only source; unsubscribe releases the caller's observation.
*/
observeChanges(packageName: string): HostObservable<boolean> {
return { getSnapshot: () => bundleBusy(this.getSnapshot(), packageName), subscribe: listener => this.store.subscribe(listener) }
return {
getSnapshot: () => {
const state = this.getSnapshot()
return bundleBusy(state, packageName) || (state.packages.find(pkg => pkg.name === packageName)?.rows.some(row =>
row.entryId !== undefined && state.busy.includes(rowKey(row.entryId))) ?? false)
},
subscribe: listener => this.store.subscribe(listener),
}
}
/** Stop publishing and drop every late settlement. */
@@ -134,7 +134,7 @@ it('shows query failures as unknown while retaining previously displayed apps',
fireEvent.click(screen.getByRole('button', { name: en.retry }))
expect(f.props.refresh).toHaveBeenCalledTimes(2)
f.publish({ system: { ...f.state.getSnapshot().system, busy: true }, busy: true })
expect(screen.queryByRole('button', { name: en.retry })).toBeNull()
expect(screen.getByRole('button', { name: en.retry }).hasAttribute('disabled')).toBe(true)
f.publish({ system: { ...f.state.getSnapshot().system, busy: false, error: null }, busy: false })
expect(screen.queryByRole('button', { name: en.retry })).toBeNull()
f.publish(observe(value))
@@ -292,7 +292,7 @@ it('keeps content mounted and disables actions while management changes are pend
const requirements = screen.getByRole('region', { name: en.title })
expect(within(requirements).getAllByRole('group')).toHaveLength(4)
expect(screen.getByRole('alert').textContent).toContain('provider is reloading')
expect(screen.getByRole('button', { name: en.unknown }).hasAttribute('disabled')).toBe(true)
expect(within(screen.getByRole('group', { name: en.accessibility })).getByRole('button', { name: en.unknown }).hasAttribute('disabled')).toBe(true)
fireEvent.focus(window)
expect(f.props.refresh).toHaveBeenCalledTimes(2)
f.publish({ system: { ...f.state.getSnapshot().system, busy: true, error: null }, busy: true })
@@ -388,14 +388,13 @@ it('loads the runtime setting independently and reports unavailable and read-onl
})
it('retains the previous single control while a stale query error is being refreshed', () => {
it('retains uncertainty and the diagnostic until a refresh succeeds', () => {
const f = fixture()
f.publish(observe(value))
render(<PermissionPanel {...f.props} />)
f.publish({ system: { value: value.system, busy: true, error: 'stale error' }, busy: true })
expect(screen.getByRole('button', { name: en.request }).hasAttribute('disabled')).toBe(true)
expect(screen.getByRole('button', { name: en.granted }).hasAttribute('disabled')).toBe(true)
expect(screen.queryByRole('alert')).toBeNull()
for (const button of screen.getAllByRole('button', { name: en.unknown })) expect(button.hasAttribute('disabled')).toBe(true)
expect(screen.getByRole('alert').textContent).toContain('stale error')
})
+1
View File
@@ -197,6 +197,7 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the API',
layout: 'client-side interface-typed browser service — packages/client/ui-layout/README.md owns the API',
pluginNavigation: 'client-side bundle navigation — packages/client/ui-plugin-manager/README.md owns the API',
pluginChanges: 'client-side read-only management activity — packages/client/ui-plugin-manager/README.md owns the API',
locale: 'client-side interface-typed browser service — packages/client/locale/README.md owns the API',
modelDirectories: 'client-side interface-typed browser service — packages/client/ui-model-selection/README.md owns the API',
modules: 'client-side interface-typed browser service — packages/client/modules/README.md owns the API',