mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Make help easier to find and cloned workspaces open reliably (#2045)
* feat(app): add sidebar help and support menu Give users a single place to run diagnostics, find shortcuts, and reach the support channels. Share one global diagnostic sheet between Settings and the sidebar so both entry points collect the same report. * fix(app): keep cloned workspace navigation atomic A newly merged clone flow still used the previous navigation signature and opened its draft tab separately. Route the cloned workspace and its draft target through the unified navigation command so attention selection cannot replace the intended tab. * test(app): harden sidebar help coverage Accept both the Discord invite URL and its canonical redirect, assert placement through real element geometry, and remove an unnecessary diagnostic-host callback. * test(app): cover support redirect variants Keep the help-menu E2E stable across prerelease versions and the valid redirect forms returned by Discord and GitHub.
This commit is contained in:
100
packages/app/e2e/sidebar-help.spec.ts
Normal file
100
packages/app/e2e/sidebar-help.spec.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { expect, test, type Page } from "./fixtures";
|
||||
import { gotoAppShell, openSettings } from "./helpers/app";
|
||||
import { openSettingsSection } from "./helpers/settings";
|
||||
|
||||
const DISCORD_DESTINATION =
|
||||
/^https:\/\/(?:discord\.gg\/jz8T2uahpH|discord\.com\/invite\/jz8T2uahpH)(?:[/?#]|$)/;
|
||||
const GITHUB_ISSUE_DESTINATION =
|
||||
/^https:\/\/github\.com\/(?:getpaseo\/paseo\/issues\/new(?:\/choose)?(?:[/?#]|$)|login\?return_to=https%3A%2F%2Fgithub\.com%2Fgetpaseo%2Fpaseo%2Fissues%2Fnew$)/;
|
||||
const APP_VERSION = /^Paseo v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
||||
|
||||
async function openHelpMenu(page: Page): Promise<void> {
|
||||
await page.getByTestId("sidebar-help").click();
|
||||
await expect(page.getByTestId("sidebar-help-menu")).toBeVisible();
|
||||
}
|
||||
|
||||
async function expectDiagnosticReport(page: Page): Promise<void> {
|
||||
const sheet = page.getByTestId("app-diagnostic-sheet");
|
||||
await expect(sheet).toBeVisible();
|
||||
await expect(sheet.getByRole("button", { name: "Copy diagnostic" })).toBeEnabled();
|
||||
await expect(page.getByText(/App version:/).first()).toBeVisible();
|
||||
}
|
||||
|
||||
async function closeSheet(page: Page, testID: string): Promise<void> {
|
||||
const sheet = page.getByTestId(testID);
|
||||
await sheet.getByLabel("Close").click();
|
||||
await expect(sheet).not.toBeVisible();
|
||||
}
|
||||
|
||||
async function expectExternalPage(
|
||||
page: Page,
|
||||
actionTestID: string,
|
||||
expectedUrl: RegExp,
|
||||
): Promise<void> {
|
||||
const popupPromise = page.waitForEvent("popup");
|
||||
await page.getByTestId(actionTestID).click();
|
||||
const popup = await popupPromise;
|
||||
expect(popup.url()).toMatch(expectedUrl);
|
||||
await popup.close();
|
||||
}
|
||||
|
||||
test("opens troubleshooting tools from the sidebar help menu", async ({ page }) => {
|
||||
await gotoAppShell(page);
|
||||
await expect(page.getByTestId("sidebar-help")).toBeVisible();
|
||||
|
||||
await openHelpMenu(page);
|
||||
const triggerBox = await page.getByTestId("sidebar-help").evaluate((element) => {
|
||||
const { y, height } = element.getBoundingClientRect();
|
||||
return { y, height };
|
||||
});
|
||||
const menuBox = await page.getByTestId("sidebar-help-menu").evaluate((element) => {
|
||||
const { y, height } = element.getBoundingClientRect();
|
||||
return { y, height };
|
||||
});
|
||||
expect(menuBox.y + menuBox.height).toBeLessThanOrEqual(triggerBox.y);
|
||||
await expect(page.getByText("Troubleshoot", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Report an issue", { exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("sidebar-help-version")).toHaveText(APP_VERSION);
|
||||
|
||||
await page.getByTestId("sidebar-help-diagnostics").click();
|
||||
await expectDiagnosticReport(page);
|
||||
await closeSheet(page, "app-diagnostic-sheet");
|
||||
|
||||
await openHelpMenu(page);
|
||||
await page.getByTestId("sidebar-help-shortcuts").click();
|
||||
await expect(page.getByTestId("keyboard-shortcuts-dialog")).toBeVisible();
|
||||
await closeSheet(page, "keyboard-shortcuts-dialog");
|
||||
});
|
||||
|
||||
test("opens the preferred issue-reporting destinations", async ({ page }) => {
|
||||
await gotoAppShell(page);
|
||||
|
||||
await openHelpMenu(page);
|
||||
await expectExternalPage(page, "sidebar-help-discord", DISCORD_DESTINATION);
|
||||
|
||||
await openHelpMenu(page);
|
||||
await expectExternalPage(page, "sidebar-help-github", GITHUB_ISSUE_DESTINATION);
|
||||
});
|
||||
|
||||
test("keeps diagnostics available from Settings after globalizing the sheet", async ({ page }) => {
|
||||
await gotoAppShell(page);
|
||||
await openSettings(page);
|
||||
await openSettingsSection(page, "diagnostics");
|
||||
|
||||
await page.getByRole("button", { name: "Run", exact: true }).click();
|
||||
await expectDiagnosticReport(page);
|
||||
});
|
||||
|
||||
test.describe("compact sidebar help", () => {
|
||||
test.use({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });
|
||||
|
||||
test("offers diagnostics without advertising disabled keyboard shortcuts", async ({ page }) => {
|
||||
await gotoAppShell(page);
|
||||
await page.getByRole("button", { name: "Open menu", exact: true }).click();
|
||||
|
||||
await openHelpMenu(page);
|
||||
await expect(page.getByTestId("sidebar-help-shortcuts")).toHaveCount(0);
|
||||
await page.getByTestId("sidebar-help-diagnostics").click();
|
||||
await expectDiagnosticReport(page);
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,7 @@ import { WorktreeSetupCalloutSource } from "@/components/worktree-setup-callout-
|
||||
import { DownloadToast } from "@/components/download-toast";
|
||||
import { QuittingOverlay } from "@/components/quitting-overlay";
|
||||
import { KeyboardShortcutsDialog } from "@/components/keyboard-shortcuts-dialog";
|
||||
import { AppDiagnosticHost } from "@/components/app-diagnostic-host";
|
||||
import { LeftSidebar } from "@/components/left-sidebar";
|
||||
import { SidebarModelProvider } from "@/components/sidebar/sidebar-model";
|
||||
import { CompactExplorerSidebarHost } from "@/components/compact-explorer-sidebar-host";
|
||||
@@ -487,6 +488,7 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
|
||||
<ProviderSettingsHost />
|
||||
<WorkspaceSetupDialog />
|
||||
<KeyboardShortcutsDialog />
|
||||
<AppDiagnosticHost />
|
||||
<QuittingOverlay />
|
||||
</View>
|
||||
);
|
||||
|
||||
18
packages/app/src/components/app-diagnostic-host.tsx
Normal file
18
packages/app/src/components/app-diagnostic-host.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { AppDiagnosticSheet } from "@/components/app-diagnostic-sheet";
|
||||
import { isElectronRuntime } from "@/desktop/host";
|
||||
import { useAppDiagnosticStore } from "@/diagnostics/store";
|
||||
import { resolveAppVersion } from "@/utils/app-version";
|
||||
|
||||
export function AppDiagnosticHost() {
|
||||
const visible = useAppDiagnosticStore((state) => state.visible);
|
||||
const close = useAppDiagnosticStore((state) => state.close);
|
||||
|
||||
return (
|
||||
<AppDiagnosticSheet
|
||||
visible={visible}
|
||||
onClose={close}
|
||||
appVersion={resolveAppVersion()}
|
||||
isDesktopApp={isElectronRuntime()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||
import { HostPicker } from "@/components/hosts/host-picker";
|
||||
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
|
||||
import { SidebarDisplayPreferencesMenu } from "@/components/sidebar/sidebar-display-preferences-menu";
|
||||
import { SidebarHelpMenu } from "@/components/sidebar/sidebar-help-menu";
|
||||
import { Shortcut } from "@/components/ui/shortcut";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
@@ -532,6 +533,7 @@ function SidebarFooter({
|
||||
shortcutKeys={settingsKeys}
|
||||
theme={theme}
|
||||
/>
|
||||
<SidebarHelpMenu />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
156
packages/app/src/components/sidebar/sidebar-help-menu.tsx
Normal file
156
packages/app/src/components/sidebar/sidebar-help-menu.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Activity, CircleHelp, Keyboard } from "lucide-react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||
import { DiscordIcon } from "@/components/icons/discord-icon";
|
||||
import { GitHubIcon } from "@/components/icons/github-icon";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuHint,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { isNative } from "@/constants/platform";
|
||||
import { useAppDiagnosticStore } from "@/diagnostics/store";
|
||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||
import { ICON_SIZE, type Theme } from "@/styles/theme";
|
||||
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
||||
import { resolveAppVersion } from "@/utils/app-version";
|
||||
import { openExternalUrl } from "@/utils/open-external-url";
|
||||
|
||||
const DISCORD_URL = "https://discord.gg/jz8T2uahpH";
|
||||
const GITHUB_ISSUE_URL = "https://github.com/getpaseo/paseo/issues/new";
|
||||
const ThemedActivity = withUnistyles(Activity);
|
||||
const ThemedCircleHelp = withUnistyles(CircleHelp);
|
||||
const ThemedKeyboard = withUnistyles(Keyboard);
|
||||
const ThemedDiscordIcon = withUnistyles(DiscordIcon);
|
||||
const ThemedGitHubIcon = withUnistyles(GitHubIcon);
|
||||
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
|
||||
const foregroundMutedColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
});
|
||||
const diagnosticLeadingIcon = (
|
||||
<ThemedActivity size={ICON_SIZE.sm} uniProps={foregroundMutedColorMapping} />
|
||||
);
|
||||
const shortcutsLeadingIcon = (
|
||||
<ThemedKeyboard size={ICON_SIZE.sm} uniProps={foregroundMutedColorMapping} />
|
||||
);
|
||||
const discordLeadingIcon = (
|
||||
<ThemedDiscordIcon size={ICON_SIZE.sm} uniProps={foregroundMutedColorMapping} />
|
||||
);
|
||||
const githubLeadingIcon = (
|
||||
<ThemedGitHubIcon size={ICON_SIZE.sm} uniProps={foregroundMutedColorMapping} />
|
||||
);
|
||||
|
||||
export function SidebarHelpMenu() {
|
||||
const { t } = useTranslation();
|
||||
const isCompactLayout = useIsCompactFormFactor();
|
||||
const openAppDiagnostic = useAppDiagnosticStore((state) => state.open);
|
||||
const setShortcutsDialogOpen = useKeyboardShortcutsStore((state) => state.setShortcutsDialogOpen);
|
||||
const [open, setOpen] = useState(false);
|
||||
const showKeyboardShortcuts = !isNative && !isCompactLayout;
|
||||
const version = formatVersionWithPrefix(resolveAppVersion());
|
||||
|
||||
const openKeyboardShortcuts = useCallback(() => {
|
||||
setShortcutsDialogOpen(true);
|
||||
}, [setShortcutsDialogOpen]);
|
||||
|
||||
const openDiscord = useCallback(() => {
|
||||
void openExternalUrl(DISCORD_URL);
|
||||
}, []);
|
||||
|
||||
const openGitHubIssue = useCallback(() => {
|
||||
void openExternalUrl(GITHUB_ISSUE_URL);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<Tooltip delayDuration={300} enabledOnDesktop={!open}>
|
||||
<TooltipTrigger asChild>
|
||||
<View>
|
||||
<DropdownMenuTrigger
|
||||
style={styles.trigger}
|
||||
testID="sidebar-help"
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("sidebar.help.trigger")}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<ThemedCircleHelp
|
||||
size={ICON_SIZE.md}
|
||||
uniProps={hovered ? foregroundColorMapping : foregroundMutedColorMapping}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
</View>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<Text style={styles.tooltipText}>{t("sidebar.help.trigger")}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent side="top" align="end" offset={8} width={280} testID="sidebar-help-menu">
|
||||
<DropdownMenuLabel>{t("sidebar.help.troubleshoot")}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
testID="sidebar-help-diagnostics"
|
||||
description={t("sidebar.help.diagnosticsDescription")}
|
||||
leading={diagnosticLeadingIcon}
|
||||
onSelect={openAppDiagnostic}
|
||||
>
|
||||
{t("sidebar.help.diagnostics")}
|
||||
</DropdownMenuItem>
|
||||
{showKeyboardShortcuts ? (
|
||||
<DropdownMenuItem
|
||||
testID="sidebar-help-shortcuts"
|
||||
description={t("sidebar.help.shortcutsDescription")}
|
||||
leading={shortcutsLeadingIcon}
|
||||
onSelect={openKeyboardShortcuts}
|
||||
>
|
||||
{t("sidebar.help.shortcuts")}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>{t("sidebar.help.reportIssue")}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
testID="sidebar-help-discord"
|
||||
description={t("sidebar.help.discordDescription")}
|
||||
leading={discordLeadingIcon}
|
||||
onSelect={openDiscord}
|
||||
>
|
||||
{t("sidebar.help.discord")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
testID="sidebar-help-github"
|
||||
description={t("sidebar.help.githubDescription")}
|
||||
leading={githubLeadingIcon}
|
||||
onSelect={openGitHubIssue}
|
||||
>
|
||||
{t("sidebar.help.github")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuHint testID="sidebar-help-version">
|
||||
{t("sidebar.help.version", { version })}
|
||||
</DropdownMenuHint>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
trigger: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingVertical: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[1],
|
||||
},
|
||||
tooltipText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
}));
|
||||
13
packages/app/src/diagnostics/store.ts
Normal file
13
packages/app/src/diagnostics/store.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
interface AppDiagnosticState {
|
||||
visible: boolean;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const useAppDiagnosticStore = create<AppDiagnosticState>((set) => ({
|
||||
visible: false,
|
||||
open: () => set({ visible: true }),
|
||||
close: () => set({ visible: false }),
|
||||
}));
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
type EmptyProjectDescriptor as ProjectWithoutWorkspacesDescriptor,
|
||||
type WorkspaceDescriptor,
|
||||
} from "@/stores/session-store";
|
||||
import { generateDraftId } from "@/stores/draft-keys";
|
||||
import type { NavigateToWorkspaceInput } from "@/stores/navigation-active-workspace-store";
|
||||
import { buildWorkspaceTabPersistenceKey } from "@/stores/workspace-tabs-store";
|
||||
|
||||
type OpenProjectPayload = ProjectAddResponse["payload"];
|
||||
@@ -57,8 +59,7 @@ interface WorkspaceOpenCallbacks {
|
||||
isConnected: boolean;
|
||||
mergeWorkspaces: (serverId: string, workspaces: Iterable<WorkspaceDescriptor>) => void;
|
||||
setHasHydratedWorkspaces: (serverId: string, hydrated: boolean) => void;
|
||||
openDraftTab: (workspaceKey: string) => string | null;
|
||||
navigateToWorkspace: (serverId: string, workspaceId: string) => void;
|
||||
navigateToWorkspace: (input: NavigateToWorkspaceInput) => string;
|
||||
}
|
||||
|
||||
export interface OpenGithubRepoDirectlyInput extends WorkspaceOpenCallbacks {
|
||||
@@ -122,8 +123,11 @@ function finishWorkspaceOpen(
|
||||
|
||||
input.mergeWorkspaces(normalizedServerId, [workspace]);
|
||||
input.setHasHydratedWorkspaces(normalizedServerId, true);
|
||||
input.openDraftTab(workspaceKey);
|
||||
input.navigateToWorkspace(normalizedServerId, workspace.id);
|
||||
input.navigateToWorkspace({
|
||||
serverId: normalizedServerId,
|
||||
workspaceId: workspace.id,
|
||||
target: { kind: "draft", draftId: generateDraftId() },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
EmptyProjectDescriptor as ProjectWithoutWorkspacesDescriptor,
|
||||
WorkspaceDescriptor,
|
||||
} from "@/stores/session-store";
|
||||
import type { NavigateToWorkspaceInput } from "@/stores/navigation-active-workspace-store";
|
||||
|
||||
const SERVER_ID = "server-1";
|
||||
const PROJECT_PATH = "/repo/project";
|
||||
@@ -55,15 +56,6 @@ interface RecordedHydrated {
|
||||
hydrated: boolean;
|
||||
}
|
||||
|
||||
interface RecordedOpenDraftTab {
|
||||
workspaceKey: string;
|
||||
}
|
||||
|
||||
interface RecordedNavigate {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
interface RecordedClone {
|
||||
repo: string;
|
||||
targetDirectory: string;
|
||||
@@ -90,23 +82,13 @@ function createFakeSession() {
|
||||
};
|
||||
}
|
||||
|
||||
function createFakeWorkspaceLayout() {
|
||||
const openedTabs: RecordedOpenDraftTab[] = [];
|
||||
return {
|
||||
openedTabs,
|
||||
openDraftTab: (workspaceKey: string) => {
|
||||
openedTabs.push({ workspaceKey });
|
||||
return "tab-1";
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createFakeNavigator() {
|
||||
const navigations: RecordedNavigate[] = [];
|
||||
const navigations: NavigateToWorkspaceInput[] = [];
|
||||
return {
|
||||
navigations,
|
||||
navigateToWorkspace: (serverId: string, workspaceId: string) => {
|
||||
navigations.push({ serverId, workspaceId });
|
||||
navigateToWorkspace: (input: NavigateToWorkspaceInput) => {
|
||||
navigations.push(input);
|
||||
return `/hosts/${input.serverId}/workspaces/${input.workspaceId}`;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -226,7 +208,6 @@ describe("openProjectDirectly", () => {
|
||||
describe("openGithubRepoDirectly", () => {
|
||||
it("opens a cloned GitHub workspace and seeds a draft tab", async () => {
|
||||
const session = createFakeSession();
|
||||
const layout = createFakeWorkspaceLayout();
|
||||
const navigator = createFakeNavigator();
|
||||
const workspacePayload = buildWorkspacePayload();
|
||||
const github = createFakeGithubCloneClient(workspacePayload);
|
||||
@@ -240,7 +221,6 @@ describe("openGithubRepoDirectly", () => {
|
||||
client: github,
|
||||
mergeWorkspaces: session.mergeWorkspaces,
|
||||
setHasHydratedWorkspaces: session.setHasHydratedWorkspaces,
|
||||
openDraftTab: layout.openDraftTab,
|
||||
navigateToWorkspace: navigator.navigateToWorkspace,
|
||||
});
|
||||
|
||||
@@ -261,13 +241,17 @@ describe("openGithubRepoDirectly", () => {
|
||||
workspaceDirectory: PROJECT_PATH,
|
||||
});
|
||||
expect(session.hydrated).toEqual([{ serverId: SERVER_ID, hydrated: true }]);
|
||||
expect(layout.openedTabs).toEqual([{ workspaceKey: `${SERVER_ID}:1` }]);
|
||||
expect(navigator.navigations).toEqual([{ serverId: SERVER_ID, workspaceId: "1" }]);
|
||||
expect(navigator.navigations).toEqual([
|
||||
{
|
||||
serverId: SERVER_ID,
|
||||
workspaceId: "1",
|
||||
target: { kind: "draft", draftId: expect.any(String) },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects a workspace without an identity before changing app state", async () => {
|
||||
const session = createFakeSession();
|
||||
const layout = createFakeWorkspaceLayout();
|
||||
const navigator = createFakeNavigator();
|
||||
const github = createFakeGithubCloneClient({ ...buildWorkspacePayload(), id: " " });
|
||||
|
||||
@@ -280,14 +264,12 @@ describe("openGithubRepoDirectly", () => {
|
||||
client: github,
|
||||
mergeWorkspaces: session.mergeWorkspaces,
|
||||
setHasHydratedWorkspaces: session.setHasHydratedWorkspaces,
|
||||
openDraftTab: layout.openDraftTab,
|
||||
navigateToWorkspace: navigator.navigateToWorkspace,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(session.merges).toEqual([]);
|
||||
expect(session.hydrated).toEqual([]);
|
||||
expect(layout.openedTabs).toEqual([]);
|
||||
expect(navigator.navigations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { useWorkspaceLayoutStore } from "@/stores/workspace-layout-store";
|
||||
import { generateDraftId } from "@/stores/draft-keys";
|
||||
import { navigateToWorkspace } from "@/stores/navigation-active-workspace-store";
|
||||
import {
|
||||
openGithubRepoDirectly,
|
||||
@@ -61,12 +59,6 @@ export function useOpenGithubRepo(
|
||||
const isConnected = useHostRuntimeIsConnected(normalizedServerId);
|
||||
const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces);
|
||||
const setHasHydratedWorkspaces = useSessionStore((state) => state.setHasHydratedWorkspaces);
|
||||
const openDraftTab = useCallback((workspaceKey: string) => {
|
||||
return useWorkspaceLayoutStore.getState().openTabFocused(workspaceKey, {
|
||||
kind: "draft",
|
||||
draftId: generateDraftId(),
|
||||
});
|
||||
}, []);
|
||||
|
||||
return useCallback(
|
||||
async (repo: string, targetDirectory: string, cloneProtocol?: WorkspaceGithubCloneProtocol) => {
|
||||
@@ -79,17 +71,9 @@ export function useOpenGithubRepo(
|
||||
client,
|
||||
mergeWorkspaces,
|
||||
setHasHydratedWorkspaces,
|
||||
openDraftTab,
|
||||
navigateToWorkspace,
|
||||
});
|
||||
},
|
||||
[
|
||||
client,
|
||||
isConnected,
|
||||
mergeWorkspaces,
|
||||
normalizedServerId,
|
||||
openDraftTab,
|
||||
setHasHydratedWorkspaces,
|
||||
],
|
||||
[client, isConnected, mergeWorkspaces, normalizedServerId, setHasHydratedWorkspaces],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -790,6 +790,20 @@ export const ar: TranslationResources = {
|
||||
settings: "إعدادات",
|
||||
closeSidebar: "إغلاق الشريط الجانبي",
|
||||
},
|
||||
help: {
|
||||
trigger: "المساعدة والدعم",
|
||||
troubleshoot: "استكشاف الأخطاء وإصلاحها",
|
||||
diagnostics: "تشغيل التشخيص",
|
||||
diagnosticsDescription: "جمع تفاصيل التطبيق والمضيفين المتصلين",
|
||||
shortcuts: "اختصارات لوحة المفاتيح",
|
||||
shortcutsDescription: "عرض اختصارات لوحة المفاتيح المتاحة",
|
||||
reportIssue: "الإبلاغ عن مشكلة",
|
||||
discord: "Discord",
|
||||
discordDescription: "الأفضل للمساعدة السريعة والنقاش",
|
||||
github: "إنشاء مشكلة على GitHub",
|
||||
githubDescription: "الإبلاغ عن خطأ يمكن إعادة إنتاجه",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "السجل",
|
||||
schedules: "الجداول",
|
||||
|
||||
@@ -797,6 +797,20 @@ export const en = {
|
||||
settings: "Settings",
|
||||
closeSidebar: "Close sidebar",
|
||||
},
|
||||
help: {
|
||||
trigger: "Help and support",
|
||||
troubleshoot: "Troubleshoot",
|
||||
diagnostics: "Run diagnostics",
|
||||
diagnosticsDescription: "Collect app and connected host details",
|
||||
shortcuts: "Keyboard shortcuts",
|
||||
shortcutsDescription: "View available keyboard shortcuts",
|
||||
reportIssue: "Report an issue",
|
||||
discord: "Discord",
|
||||
discordDescription: "Best for quick help and discussion",
|
||||
github: "Create GitHub issue",
|
||||
githubDescription: "Report a reproducible bug",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "History",
|
||||
schedules: "Schedules",
|
||||
|
||||
@@ -817,6 +817,20 @@ export const es: TranslationResources = {
|
||||
settings: "Ajustes",
|
||||
closeSidebar: "Cerrar barra lateral",
|
||||
},
|
||||
help: {
|
||||
trigger: "Ayuda y soporte",
|
||||
troubleshoot: "Solucionar problemas",
|
||||
diagnostics: "Ejecutar diagnóstico",
|
||||
diagnosticsDescription: "Recopila datos de la app y los hosts conectados",
|
||||
shortcuts: "Atajos de teclado",
|
||||
shortcutsDescription: "Ver los atajos de teclado disponibles",
|
||||
reportIssue: "Informar de un problema",
|
||||
discord: "Discord",
|
||||
discordDescription: "La mejor opción para ayuda rápida y conversación",
|
||||
github: "Crear incidencia en GitHub",
|
||||
githubDescription: "Informar de un error reproducible",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "Historial",
|
||||
schedules: "Horarios",
|
||||
|
||||
@@ -816,6 +816,20 @@ export const fr: TranslationResources = {
|
||||
settings: "Paramètres",
|
||||
closeSidebar: "Fermer la barre latérale",
|
||||
},
|
||||
help: {
|
||||
trigger: "Aide et assistance",
|
||||
troubleshoot: "Dépannage",
|
||||
diagnostics: "Lancer le diagnostic",
|
||||
diagnosticsDescription: "Collecter les détails de l’app et des hôtes connectés",
|
||||
shortcuts: "Raccourcis clavier",
|
||||
shortcutsDescription: "Afficher les raccourcis clavier disponibles",
|
||||
reportIssue: "Signaler un problème",
|
||||
discord: "Discord",
|
||||
discordDescription: "Idéal pour obtenir une aide rapide et échanger",
|
||||
github: "Créer un ticket GitHub",
|
||||
githubDescription: "Signaler un bug reproductible",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "Historique",
|
||||
schedules: "Planifications",
|
||||
|
||||
@@ -802,6 +802,20 @@ export const ja: TranslationResources = {
|
||||
settings: "設定",
|
||||
closeSidebar: "サイドバーを閉じる",
|
||||
},
|
||||
help: {
|
||||
trigger: "ヘルプとサポート",
|
||||
troubleshoot: "トラブルシューティング",
|
||||
diagnostics: "診断を実行",
|
||||
diagnosticsDescription: "アプリと接続中のホストの詳細を収集",
|
||||
shortcuts: "キーボードショートカット",
|
||||
shortcutsDescription: "利用可能なキーボードショートカットを表示",
|
||||
reportIssue: "問題を報告",
|
||||
discord: "Discord",
|
||||
discordDescription: "すばやいサポートや相談に最適",
|
||||
github: "GitHub Issueを作成",
|
||||
githubDescription: "再現可能なバグを報告",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "履歴",
|
||||
schedules: "スケジュール",
|
||||
|
||||
@@ -808,6 +808,20 @@ export const ptBR: TranslationResources = {
|
||||
settings: "Configurações",
|
||||
closeSidebar: "Fechar barra lateral",
|
||||
},
|
||||
help: {
|
||||
trigger: "Ajuda e suporte",
|
||||
troubleshoot: "Resolver problemas",
|
||||
diagnostics: "Executar diagnóstico",
|
||||
diagnosticsDescription: "Coletar detalhes do app e dos hosts conectados",
|
||||
shortcuts: "Atalhos de teclado",
|
||||
shortcutsDescription: "Ver os atalhos de teclado disponíveis",
|
||||
reportIssue: "Relatar um problema",
|
||||
discord: "Discord",
|
||||
discordDescription: "Ideal para ajuda rápida e conversa",
|
||||
github: "Criar issue no GitHub",
|
||||
githubDescription: "Relatar um bug reproduzível",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "Histórico",
|
||||
schedules: "Agendamentos",
|
||||
|
||||
@@ -809,6 +809,20 @@ export const ru: TranslationResources = {
|
||||
settings: "Настройки",
|
||||
closeSidebar: "Закрыть боковую панель",
|
||||
},
|
||||
help: {
|
||||
trigger: "Помощь и поддержка",
|
||||
troubleshoot: "Устранение неполадок",
|
||||
diagnostics: "Запустить диагностику",
|
||||
diagnosticsDescription: "Собрать данные приложения и подключённых хостов",
|
||||
shortcuts: "Сочетания клавиш",
|
||||
shortcutsDescription: "Показать доступные сочетания клавиш",
|
||||
reportIssue: "Сообщить о проблеме",
|
||||
discord: "Discord",
|
||||
discordDescription: "Для быстрой помощи и обсуждения",
|
||||
github: "Создать issue в GitHub",
|
||||
githubDescription: "Сообщить о воспроизводимой ошибке",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "История",
|
||||
schedules: "Расписания",
|
||||
|
||||
@@ -785,6 +785,20 @@ export const zhCN: TranslationResources = {
|
||||
settings: "设置",
|
||||
closeSidebar: "关闭侧边栏",
|
||||
},
|
||||
help: {
|
||||
trigger: "帮助与支持",
|
||||
troubleshoot: "问题排查",
|
||||
diagnostics: "运行诊断",
|
||||
diagnosticsDescription: "收集应用和已连接 Host 的详细信息",
|
||||
shortcuts: "键盘快捷键",
|
||||
shortcutsDescription: "查看可用的键盘快捷键",
|
||||
reportIssue: "报告问题",
|
||||
discord: "Discord",
|
||||
discordDescription: "适合快速求助和讨论",
|
||||
github: "创建 GitHub Issue",
|
||||
githubDescription: "报告可复现的 bug",
|
||||
version: "Paseo {{version}}",
|
||||
},
|
||||
sections: {
|
||||
sessions: "历史",
|
||||
schedules: "计划",
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
SquareTerminal,
|
||||
} from "lucide-react-native";
|
||||
import { DropdownTrigger } from "@/components/ui/dropdown-trigger";
|
||||
import { AppDiagnosticSheet } from "@/components/app-diagnostic-sheet";
|
||||
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
|
||||
import { SidebarSeparator } from "@/components/sidebar/sidebar-separator";
|
||||
@@ -76,6 +75,7 @@ import { isElectronRuntime } from "@/desktop/host";
|
||||
import { useDesktopAppUpdater } from "@/desktop/updates/use-desktop-app-updater";
|
||||
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
||||
import { resolveAppVersion } from "@/utils/app-version";
|
||||
import { useAppDiagnosticStore } from "@/diagnostics/store";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
import { THINKING_TONE_NATIVE_PCM_BASE64 } from "@/utils/thinking-tone.native-pcm";
|
||||
import { useVoiceAudioEngineOptional } from "@/contexts/voice-context";
|
||||
@@ -445,8 +445,6 @@ interface DiagnosticsSectionProps {
|
||||
isPlaybackTestRunning: boolean;
|
||||
playbackTestResult: string | null;
|
||||
handlePlaybackTest: () => Promise<void>;
|
||||
appVersion: string | null;
|
||||
isDesktopApp: boolean;
|
||||
}
|
||||
|
||||
function DiagnosticsSection({
|
||||
@@ -454,16 +452,12 @@ function DiagnosticsSection({
|
||||
isPlaybackTestRunning,
|
||||
playbackTestResult,
|
||||
handlePlaybackTest,
|
||||
appVersion,
|
||||
isDesktopApp,
|
||||
}: DiagnosticsSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const [diagnosticSheetOpen, setDiagnosticSheetOpen] = useState(false);
|
||||
const openAppDiagnostic = useAppDiagnosticStore((state) => state.open);
|
||||
const handlePlayPress = useCallback(() => {
|
||||
void handlePlaybackTest();
|
||||
}, [handlePlaybackTest]);
|
||||
const handleOpenDiagnostic = useCallback(() => setDiagnosticSheetOpen(true), []);
|
||||
const handleCloseDiagnostic = useCallback(() => setDiagnosticSheetOpen(false), []);
|
||||
return (
|
||||
<SettingsSection title={t("settings.diagnostics.title")}>
|
||||
<View style={settingsStyles.card}>
|
||||
@@ -472,7 +466,7 @@ function DiagnosticsSection({
|
||||
<Text style={settingsStyles.rowTitle}>{t("settings.diagnostics.app.rowTitle")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>{t("settings.diagnostics.app.rowHint")}</Text>
|
||||
</View>
|
||||
<Button variant="secondary" size="sm" onPress={handleOpenDiagnostic}>
|
||||
<Button variant="secondary" size="sm" onPress={openAppDiagnostic}>
|
||||
{t("settings.diagnostics.app.run")}
|
||||
</Button>
|
||||
</View>
|
||||
@@ -495,12 +489,6 @@ function DiagnosticsSection({
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<AppDiagnosticSheet
|
||||
visible={diagnosticSheetOpen}
|
||||
onClose={handleCloseDiagnostic}
|
||||
appVersion={appVersion}
|
||||
isDesktopApp={isDesktopApp}
|
||||
/>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
@@ -1429,8 +1417,6 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti
|
||||
isPlaybackTestRunning={isPlaybackTestRunning}
|
||||
playbackTestResult={playbackTestResult}
|
||||
handlePlaybackTest={handlePlaybackTest}
|
||||
appVersion={appVersion}
|
||||
isDesktopApp={isDesktopApp}
|
||||
/>
|
||||
);
|
||||
case "about":
|
||||
|
||||
Reference in New Issue
Block a user