Merge pull request #6594 from deepseek-harness/turtle/plugin-market-enable-stay

fix(plugin-manager): keep the selected tab when enabling a bundle
This commit is contained in:
Turtle authored and GitHub committed 2026-10-19 18:53:33 +08:00
commit 8d00245b8e
5 files changed
+35 -11

No files matched your search

@@ -9,8 +9,8 @@
en: edefff7cd986a509
zh: a270839fa86976a2
/agent-note-guided-plugin-installation/decision:
en: 30f1e636973df06a
zh: 0f874b88a5d4d7f5
en: 5448b9f8aaa9a569
zh: 9024c5328c5da3bd
/agent-note-guided-plugin-installation/alternatives-considered:
en: be64b541f4d81e7c
zh: 8600531c08ab4288
@@ -20,7 +20,7 @@ The install dialog put a spec straight into `pnpm add` and showed pnpm's termina
**Cancellation is the manager's, not the signal's.** The dialog generates a request id for each run; the Host streams the run's output and phases under it, and `cancelInstall` answers only after pnpm exited and the files are back. The dialog waits for that answer before offering the spec again; an aborted RPC or a lost connection is not a confirmation. Only the check takes a trailing `AbortSignal`: going back or closing drops a registry lookup, whose settlement nothing waits for.
**Enabling comes after the fact.** The run installs with `enabled: false`; the finished screen offers **Enable now** for the bundle it added, and the dialog closes and the list scrolls to it. Nothing is enabled before the person has seen what was installed.
**Enabling comes after the fact.** The run installs with `enabled: false`; the finished screen offers **Enable now** for the bundle it added; the dialog closes, and the list scrolls to it when its card is on the current page. Nothing is enabled before the person has seen what was installed.
**Outcomes of the moment are toasts.** A change that waits for the next start, one a higher layer overrides, a cancelled run, and a refused action each toast and retire; nothing stays on the page.
@@ -20,7 +20,7 @@ Status: implemented
**取消属于管理器,不属于信号。** 对话框为每次运行生成一个 request id;宿主在它之下流式转发运行的输出与阶段,`cancelInstall` 只在 pnpm 退出且文件恢复后才答复。对话框等到这个答复才把 spec 重新交回;中止的 RPC 或断开的连接都不算确认。只有检查末尾接受 `AbortSignal`:返回编辑或关闭会丢掉一次注册表查询,没有人等它的结果。
**启用在事后。** 运行以 `enabled: false` 安装;完成画面为它新增的组合包提供**立即启用**,对话框关闭,列表滚动到它。在人看到装了什么之前,不会启用任何东西。
**启用在事后。** 运行以 `enabled: false` 安装;完成画面为它新增的组合包提供**立即启用**,对话框关闭;其卡片在当前页面时,列表滚动到它。在人看到装了什么之前,不会启用任何东西。
**当下的结果是 toast。** 要等下次启动的变更、被更高层覆盖的变更、被取消的运行、被拒绝的操作,各弹一条 toast 后自行消失;页面上不留任何东西。
@@ -1551,8 +1551,6 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode {
// A contributed tab that left the ledger falls back to Installed, and stays there when it returns.
const tab = tabs.some(item => item.id === selectedTab) ? selectedTab : INSTALLED_TAB
useEffect(() => { if (tab !== selectedTab) setTab(tab) }, [tab, selectedTab, setTab])
// A bundle an install just enabled is shown where its card is.
useEffect(() => { if (state.highlight !== null) setTab(INSTALLED_TAB) }, [state.highlight, setTab])
const tabsId = useId()
const [activation, setActivation] = useState<string | null>(null)
useEffect(() => { ensure() }, [ensure])
@@ -1815,7 +1813,8 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode {
</div>
)
: null}
{showsCards && activated !== undefined && !state.install.open
{/* Activation guidance belongs beside the Installed list that holds the enabled bundle's card. */}
{installedCards && activated !== undefined && !state.install.open
? renderSlot('plugins.bundle.activation', {
packageName: activated.name,
onDismiss: () => { setActivation(null) },
@@ -216,7 +216,7 @@ function renderTab(
describe('PluginManagerPage', () => {
it('shows no tabs without contributions, and switches between Installed and a contributed tab', () => {
const { actions, navigation, setTabs, set } = renderTab({ packages: [pkg()] }, {}, {
const { actions, navigation, setTabs } = renderTab({ packages: [pkg()] }, {}, {
'plugins.tab:discover': (_view, owner) => {
const tab = owner as PluginTabProps
return (
@@ -247,13 +247,20 @@ describe('PluginManagerPage', () => {
setTabs([])
expect(document.querySelector('[data-plugin-group="bundles"]')).not.toBeNull()
expect(navigation.getSnapshot().tab).toBe('installed')
// Enabling a bundle an install added highlights its card, so the page shows Installed.
// A contributed tab that returns can be selected again.
setTabs([{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(screen.getByRole('tabpanel', { name: 'Discover' })).toBeTruthy()
})
it('marks the bundle an install enabled without leaving a contributed tab', () => {
const { navigation, set } = renderTab({ packages: [pkg()] }, {}, {}, {}, [{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(screen.getByRole('tabpanel', { name: 'Discover' })).toBeTruthy()
// Enable now publishes the new bundle's mark; the mark scrolls to a visible card and selects no page.
set({ highlight: 'dsh-better-sidebar' })
expect(navigation.getSnapshot().tab).toBe('installed')
expect(screen.getByRole('tabpanel', { name: en.installedTab })).toBeTruthy()
expect(navigation.getSnapshot().tab).toBe('discover')
expect(screen.getByRole('tabpanel', { name: 'Discover' })).toBeTruthy()
})
it('keeps the Installed tab panel through loading and the More list out of the tabs', () => {
@@ -2318,6 +2325,24 @@ it('dismisses activation guidance until the user enables the bundle again', () =
expect(screen.getByText('Later')).toBeTruthy()
})
it('keeps activation guidance with the Installed list when a contributed tab enables the bundle', () => {
const name = 'dsh-better-sidebar'
const subject = { spec: `${name}@0.16.0`, status: 'accepted', kind: 'registry', name, bundle: true, registry: null } as const
const { set } = renderTab({
install: { ...IDLE_INSTALL, open: true, phase: 'done', subject, installed: name },
}, { bundles: new Set([name]) }, {
[`plugins.bundle.activation:${name}`]: () => <div>Enable guidance</div>,
}, {}, [{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('button', { name: en.installEnableNow }))
set({ install: IDLE_INSTALL, packages: [pkg()] })
expect(screen.getByText('Enable guidance')).toBeTruthy()
// A contributed tab shows its own content, not guidance for the Installed list.
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(screen.queryByText('Enable guidance')).toBeNull()
fireEvent.click(screen.getByRole('tab', { name: en.installedTab }))
expect(screen.getByText('Enable guidance')).toBeTruthy()
})
it('supplies the accepted entry values and atomic mutation action to a custom plugin page', () => {
const mutate = vi.fn(async () => true)
const form: ConfigPageForm = {