Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions desktop/src/app/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ import { chromeCssVarDefaults } from "@/shared/layout/chromeLayout";
import { cn } from "@/shared/lib/cn";
import { hasPrimaryShortcutModifier } from "@/shared/lib/platform";
import { useMessageDeepLinks } from "@/shared/useMessageDeepLinks";
import { ConnectionBanner } from "@/shared/ui/ConnectionBanner";
import { SidebarInset, SidebarProvider } from "@/shared/ui/sidebar";
import { RelayConnectionOverlay } from "@/app/RelayConnectionOverlay";

const LazySettingsScreen = React.lazy(async () => {
const module = await import("@/features/settings/ui/SettingsScreen");
Expand Down Expand Up @@ -821,13 +821,19 @@ export function AppShell() {
style={chromeCssVarDefaults}
>
<div className="relative z-10 mb-2 ml-px mr-2 mt-px flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl bg-background shadow-[-1px_-1px_0_0_hsl(var(--sidebar-border)/0.45)]">
<ConnectionBanner
errorMessage={channelsErrorMessage}
/>
<Outlet />
</div>
</SidebarInset>
</MainInsetProvider>
<RelayConnectionOverlay
errorMessage={channelsErrorMessage}
hasWorkspaceRail={
workspaceRailEnabled &&
workspacesHook.workspaces.length > 1
}
isHuddleDrawerOpen={isHuddleDrawerOpen}
relayUrl={workspacesHook.activeWorkspace?.relayUrl}
/>
</div>
)}
<AppShellOverlays
Expand Down
62 changes: 62 additions & 0 deletions desktop/src/app/RelayConnectionOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { AnimatePresence, motion } from "motion/react";

import { SidebarRelayConnectionCard } from "@/features/sidebar/ui/SidebarRelayConnectionCard";
import { useSidebarRelayConnectionCard } from "@/features/sidebar/ui/useSidebarRelayConnectionCard";
import { cn } from "@/shared/lib/cn";
import { useSidebar } from "@/shared/ui/sidebar";

type RelayConnectionOverlayProps = {
errorMessage?: string;
hasWorkspaceRail?: boolean;
isHuddleDrawerOpen?: boolean;
relayUrl?: string | null;
};

/**
* Fixed bottom-left overlay that shows the relay reconnect card when the
* sidebar is collapsed. When the sidebar is open, the card lives in the
* sidebar footer instead (and this overlay is hidden). Offsets itself for
* the workspace rail (48px) and huddle drawer when present.
*/
export function RelayConnectionOverlay({
errorMessage,
hasWorkspaceRail,
isHuddleDrawerOpen,
relayUrl,
}: RelayConnectionOverlayProps) {
const card = useSidebarRelayConnectionCard(errorMessage, relayUrl);
Comment thread
klopez4212 marked this conversation as resolved.
Outdated
Comment thread
klopez4212 marked this conversation as resolved.
Outdated
const { open: sidebarOpen } = useSidebar();

const shouldShow = card.showSidebarRelayConnectionCard && !sidebarOpen;
Comment thread
klopez4212 marked this conversation as resolved.
Outdated

return (
<AnimatePresence>
{shouldShow ? (
<motion.div
animate={{ opacity: 1, y: 0 }}
className={cn(
"pointer-events-none fixed z-50 w-[284px]",
hasWorkspaceRail ? "left-[60px]" : "left-3",
isHuddleDrawerOpen
? "bottom-[calc(var(--buzz-huddle-drawer-height,0px)+12px)]"
: "bottom-3",
)}
exit={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: -20 }}
key="relay-connection-overlay"
transition={{ duration: 0.25, ease: [0.22, 1, 0.36, 1] }}
>
<div className="pointer-events-auto rounded-xl bg-background shadow-md">
<SidebarRelayConnectionCard
Comment thread
klopez4212 marked this conversation as resolved.
isConnected={card.isRelayConnectionSuccess}
isReconnectPending={card.isRelayReconnectPending}
isWaitingOnReconnectHook={card.isWaitingOnReconnectHook}
onDismiss={card.onDismissRelayConnectionCard}
onReconnect={card.onReconnectRelay}
/>
</div>
</motion.div>
) : null}
</AnimatePresence>
);
}
9 changes: 6 additions & 3 deletions desktop/src/features/sidebar/ui/AppSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// biome-ignore format: keep compact to stay within file size limit
import { MessageCirclePlus } from "lucide-react";
import * as React from "react";
import { AnimatePresence } from "motion/react";
import * as React from "react";
import { FeatureGate } from "@/shared/features";
import { SidebarDndContext } from "@/features/sidebar/ui/SidebarDnd";

Expand Down Expand Up @@ -62,6 +62,7 @@ import {
SidebarMenu,
SidebarMenuItem,
SidebarRail,
useSidebar,
} from "@/shared/ui/sidebar";

type CollapsibleSidebarGroup =
Expand Down Expand Up @@ -225,6 +226,7 @@ export function AppSidebar({
const activeWorkingByChannelId = useActiveWorkingChannelsById();
const { status: updateStatus } = useUpdaterContext();
const canShowSidebarUpdateCard = shouldShowSidebarUpdateCard(updateStatus);
const { open: sidebarOpen } = useSidebar();
const sidebarRelayConnectionCard = useSidebarRelayConnectionCard(
errorMessage,
activeWorkspace?.relayUrl,
Expand Down Expand Up @@ -763,9 +765,10 @@ export function AppSidebar({

<SidebarFooter>
<AnimatePresence>
{sidebarRelayConnectionCard.showSidebarRelayConnectionCard ? (
{sidebarRelayConnectionCard.showSidebarRelayConnectionCard &&
sidebarOpen ? (
Comment thread
klopez4212 marked this conversation as resolved.
Outdated
<SidebarRelayConnectionCard
className="mb-2 group-data-[collapsible=icon]:hidden"
className="mb-2"
isConnected={
sidebarRelayConnectionCard.isRelayConnectionSuccess
}
Expand Down
72 changes: 0 additions & 72 deletions desktop/src/shared/ui/ConnectionBanner.tsx

This file was deleted.

5 changes: 4 additions & 1 deletion desktop/src/shared/ui/sidebar-action-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,10 @@ export function SidebarCompactActionCard({
type="button"
>
<motion.span
className="relative top-[0.1875rem] flex min-h-10 min-w-0 flex-1 flex-col justify-center"
className={cn(
"relative flex min-h-10 min-w-0 flex-1 flex-col justify-center",
description && "top-[0.1875rem]",
)}
layout="position"
transition={contentTransition}
>
Expand Down
20 changes: 10 additions & 10 deletions desktop/tests/e2e/sidebar-relay-card.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,12 @@ test("sidebar access failures use the reconnect card", async ({ page }) => {
await expectGenericReconnectCard(page);
});

test("collapsed sidebar relay failures use the connection banner", async ({
page,
}) => {
test("collapsed sidebar still shows the reconnect card", async ({ page }) => {
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });

await page.goto("/");

// Drive degraded state so the card (and subsequently banner) appears.
// Drive degraded state so the card appears.
await setRelayConnectionState(page, "disconnected");

await expectGenericReconnectCard(page);
Expand All @@ -152,15 +150,17 @@ test("collapsed sidebar relay failures use the connection banner", async ({
page.locator('[data-state="collapsed"][data-collapsible="offcanvas"]'),
).toHaveCount(1);

const banner = page.getByTestId("connection-banner");
await expect(banner).toBeVisible();
await expect(banner).toContainText("Can't reach the relay.");
// The card remains visible via the fixed overlay even with sidebar collapsed.
const card = page.getByTestId("sidebar-relay-unreachable");
await expect(card).toBeVisible();
await expect(card).toContainText("Can't reach the relay");

await setChannelsReadError(page, null);
await page.getByTestId("connection-banner-reconnect").click();
// Drive connected so the banner shows "Connected" and auto-dismisses.
await page.getByTestId("sidebar-reconnect").click();
// Drive connected so the card shows success and auto-dismisses.
await setRelayConnectionState(page, "connected");
await expect(banner).toBeHidden({ timeout: 10_000 });
await expect(card).toContainText("Connected");
await expect(card).toBeHidden({ timeout: 10_000 });
});

test("sidebar stalled relay state uses the reconnect card", async ({
Expand Down
Loading