From 0d9577c9816e76310dd8c2371848f1fc8612e4e6 Mon Sep 17 00:00:00 2001 From: turtle1999 Date: Mon, 19 Oct 2026 18:12:26 +0800 Subject: [PATCH] fix(plugin-manager): keep the selected tab when enabling a bundle Enable now marks the added bundle for scrolling, and the page also selected Installed for that mark, so enabling from Discover left the market tab. The mark scrolls only to a card on the current page, and activation guidance renders only beside the Installed list. --- ...09-15-guided-plugin-installation.i18n.yaml | 4 +-- .../2026-09-15-guided-plugin-installation.md | 2 +- ...026-09-15-guided-plugin-installation.zh.md | 2 +- .../src/client/PluginManagerPage.tsx | 5 ++- .../tests/components.client.spec.tsx | 33 ++++++++++++++++--- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml index d1e00c2cd5..511e290621 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md index c0e9e68325..40cbf6179b 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md +++ b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md index 0d914869a3..c53c138059 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md @@ -20,7 +20,7 @@ Status: implemented **取消属于管理器,不属于信号。** 对话框为每次运行生成一个 request id;宿主在它之下流式转发运行的输出与阶段,`cancelInstall` 只在 pnpm 退出且文件恢复后才答复。对话框等到这个答复才把 spec 重新交回;中止的 RPC 或断开的连接都不算确认。只有检查末尾接受 `AbortSignal`:返回编辑或关闭会丢掉一次注册表查询,没有人等它的结果。 -**启用在事后。** 运行以 `enabled: false` 安装;完成画面为它新增的组合包提供**立即启用**,对话框关闭,列表滚动到它。在人看到装了什么之前,不会启用任何东西。 +**启用在事后。** 运行以 `enabled: false` 安装;完成画面为它新增的组合包提供**立即启用**,对话框关闭;其卡片在当前页面时,列表滚动到它。在人看到装了什么之前,不会启用任何东西。 **当下的结果是 toast。** 要等下次启动的变更、被更高层覆盖的变更、被取消的运行、被拒绝的操作,各弹一条 toast 后自行消失;页面上不留任何东西。 diff --git a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx index 77be2faf9f..601483cb76 100644 --- a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx +++ b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx @@ -1516,8 +1516,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(null) useEffect(() => { ensure() }, [ensure]) @@ -1772,7 +1770,8 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode { ) : 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) }, diff --git a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx index be382f110b..f0a8f1975b 100644 --- a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx +++ b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx @@ -207,7 +207,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 ( @@ -238,13 +238,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', () => { @@ -2309,6 +2316,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}`]: () =>
Enable guidance
, + }, {}, [{ 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 = {