From 8a9b62e938d5ded29a865deec22dc6d55dcb215c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 2 Nov 2026 17:38:43 +0100 Subject: [PATCH] fix(app): finish setup on its last step and confirm undoing orchestration In the compact layout Orchestration is the last step, and Finish closed setup without applying its switches. The last step's work now runs before Finish, the same as Continue. Turning off skills now asks first, as Settings does. Removing the CLI keeps a paseo command this app didn't install and says so. A failed Paseo tools update shows a toast. Adding suggested projects no longer advances when a row was checked while the add ran. Chat sender attribution and the message preview toggle go back to the sm button: md padding is a breakpoint value, and on web its media-query rule beat the zero right padding that aligns the sender with its bubble. --- packages/app/e2e/browser/onboarding.spec.ts | 33 ++++++++++++++++ .../app/e2e/support/helpers/onboarding.ts | 14 +++++++ .../app/src/agent-skills/use-agent-skills.ts | 24 ++++++++---- .../app/src/components/community-links.tsx | 3 ++ .../app/src/components/user-message/index.tsx | 2 + .../src/desktop/hooks/use-install-status.ts | 19 +++++++-- packages/app/src/i18n/resources/ar.ts | 5 +++ packages/app/src/i18n/resources/en.ts | 7 ++++ packages/app/src/i18n/resources/es.ts | 7 ++++ packages/app/src/i18n/resources/fr.ts | 7 ++++ packages/app/src/i18n/resources/ja.ts | 7 ++++ packages/app/src/i18n/resources/ko.ts | 7 ++++ packages/app/src/i18n/resources/pt-BR.ts | 7 ++++ packages/app/src/i18n/resources/ru.ts | 7 ++++ packages/app/src/i18n/resources/zh-CN.ts | 5 +++ .../onboarding/internal/onboarding-modal.tsx | 38 ++++++++---------- .../internal/orchestration-setup.ts | 39 ++++++++++++++----- .../internal/use-project-suggestions.ts | 17 ++++++-- 18 files changed, 203 insertions(+), 45 deletions(-) diff --git a/packages/app/e2e/browser/onboarding.spec.ts b/packages/app/e2e/browser/onboarding.spec.ts index fdb57741e9..df5ad71c05 100644 --- a/packages/app/e2e/browser/onboarding.spec.ts +++ b/packages/app/e2e/browser/onboarding.spec.ts @@ -8,6 +8,7 @@ import { expectSetupStep, finishSetup, goToSetupStep, + hostGivesAgentsPaseoTools, openAppAsNewUser, openRelayPairingFromSetup, closeRelayPairing, @@ -233,3 +234,35 @@ test("setup choices carry over to Settings and the phone step pairs over relay", await host.close(); } }); + +test("in the compact layout, Finish on Orchestration applies its switches", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + const host = await startSetupHost("compact"); + // The compact sheet is the only surface on screen, and Orchestration is its last step. + const step = (title: string) => + expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible({ + timeout: 60_000, + }); + const primary = (label: string) => page.getByRole("button", { name: label, exact: true }); + + try { + await openAppAsNewUser(page, host); + await step("Welcome to Paseo"); + await primary("Continue").click(); + await step("Your agents"); + await primary("Continue").click(); + await step("Add your projects"); + await primary("Continue").click(); + + // The setup host starts with Paseo tools off; the switch starts on. + expect(await hostGivesAgentsPaseoTools(host)).toBe(false); + await step("Let agents use Paseo"); + await expect(paseoToolsSwitch(page)).toBeChecked(); + await primary("Finish").click(); + + await expect.poll(() => hostGivesAgentsPaseoTools(host), { timeout: 30_000 }).toBe(true); + await expect(page.getByRole("heading", { name: "Let agents use Paseo" })).toHaveCount(0); + } finally { + await host.close(); + } +}); diff --git a/packages/app/e2e/support/helpers/onboarding.ts b/packages/app/e2e/support/helpers/onboarding.ts index 4183fc2a59..6f1d4c53b0 100644 --- a/packages/app/e2e/support/helpers/onboarding.ts +++ b/packages/app/e2e/support/helpers/onboarding.ts @@ -3,7 +3,9 @@ import { mkdir, mkdtemp, rm, utimes, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { expect, type Locator, type Page } from "@playwright/test"; +import type { DaemonClient as InternalDaemonClient } from "@getpaseo/client/internal/daemon-client"; import { addProjectFlow, addProjectFlowInput, chooseAddProjectMethod } from "./add-project-flow"; +import { connectDaemonClient } from "./daemon-client-loader"; import { buildCreateAgentPreferences, buildSeededHost } from "./daemon-registry"; import { startIsolatedHostDaemon, type IsolatedHostDaemonOptions } from "./isolated-host-daemon"; @@ -80,6 +82,18 @@ export function paseoToolsSwitch(scope: Page | Locator): Locator { return scope.getByRole("switch", { name: "Inject Paseo tools", exact: true }); } +/** Whether the host gives its agents Paseo tools, read from the host itself. */ +export async function hostGivesAgentsPaseoTools(host: { port: number }): Promise { + const client = await connectDaemonClient< + Pick + >({ clientIdPrefix: "app-e2e-onboarding", port: host.port }); + try { + return (await client.getDaemonConfig()).config.mcp.injectIntoAgents !== false; + } finally { + await client.close(); + } +} + export async function finishSetup(page: Page): Promise { await setupModal(page).getByRole("button", { name: "Finish", exact: true }).click(); await expect(setupModal(page)).toHaveCount(0); diff --git a/packages/app/src/agent-skills/use-agent-skills.ts b/packages/app/src/agent-skills/use-agent-skills.ts index dcfec21756..836cdb73dd 100644 --- a/packages/app/src/agent-skills/use-agent-skills.ts +++ b/packages/app/src/agent-skills/use-agent-skills.ts @@ -10,6 +10,7 @@ import { useToast } from "@/contexts/toast-context"; import { useFetchQuery } from "@/data/query"; import { useHostFeature } from "@/runtime/host-features"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; +import { confirmDialog } from "@/utils/confirm-dialog"; export type AgentSkillsController = ReturnType; @@ -94,14 +95,21 @@ export function useAgentSkills(serverId: string) { ), [reconcileAsync], ); - const runUninstall = useCallback( - () => - uninstallAsync().then( - (status) => status.state === "not-installed", - () => false, - ), - [uninstallAsync], - ); + // Uninstalling deletes whole skill folders, including anything the user added inside them, so + // it always asks first. Declining resolves false, as a failure does. + const runUninstall = useCallback(async () => { + const confirmed = await confirmDialog({ + title: t("settings.host.skills.uninstallTitle"), + message: t("settings.host.skills.uninstallMessage"), + confirmLabel: t("settings.host.skills.actions.uninstall"), + destructive: true, + }); + if (!confirmed) return false; + return uninstallAsync().then( + (status) => status.state === "not-installed", + () => false, + ); + }, [t, uninstallAsync]); const saveSelection = useCallback( (selection: AgentSkillSelection, confirmedRemovals: readonly string[] = []) => saveAsync({ selection, confirmedRemovals }), diff --git a/packages/app/src/components/community-links.tsx b/packages/app/src/components/community-links.tsx index 1ce65bb6e1..1c177da4d8 100644 --- a/packages/app/src/components/community-links.tsx +++ b/packages/app/src/components/community-links.tsx @@ -56,6 +56,9 @@ export function CommunityLinks() { const styles = StyleSheet.create(() => ({ row: { flexDirection: "row", + // Shrinks and wraps rather than running off a narrow phone screen. + flexShrink: 1, + flexWrap: "wrap", justifyContent: "center", alignItems: "center", gap: 0, diff --git a/packages/app/src/components/user-message/index.tsx b/packages/app/src/components/user-message/index.tsx index 1d2e67b277..e7bef2d177 100644 --- a/packages/app/src/components/user-message/index.tsx +++ b/packages/app/src/components/user-message/index.tsx @@ -41,6 +41,7 @@ export function MessagePreview({ children }: { children: ReactNode }) { {overflowing ? (