Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Passed Zoekt index parameters via argv to preserve revision names with punctuation. [#1376](https://github.com/sourcebot-dev/sourcebot/pull/1376)
- [EE] Validated OAuth bearer token scopes before allowing access to the Sourcebot MCP resource server. [#1396](https://github.com/sourcebot-dev/sourcebot/pull/1396)
- Added HTTP security headers to all web app responses. [#1407](https://github.com/sourcebot-dev/sourcebot/pull/1407)
- Maintained the sidebar scroll position when navigating between chats instead of resetting to the top. [#1411](https://github.com/sourcebot-dev/sourcebot/pull/1411)
- Upgraded `nodemailer` to `^9.0.1`. [#1356](https://github.com/sourcebot-dev/sourcebot/pull/1356)

## [5.0.4] - 2026-06-18
Expand Down
23 changes: 21 additions & 2 deletions packages/web/src/app/(app)/@sidebar/components/sidebarBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ import { signOut } from "next-auth/react";
import { useTheme } from "next-themes";
import Link from "next/link";
import posthog from "posthog-js";
import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import { ReactNode, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Separator } from "@/components/ui/separator";
import { WhatsNewSidebarButton } from "./whatsNewSidebarButton";
import { UpgradeButton } from "./upgradeButton";
import { useIsMobile } from "@/hooks/use-mobile";

// Retains the sidebar's scroll position across route-driven remounts of the
// `@sidebar` slot. Module-level so it survives remounts but resets on reload.
let lastScrollTop = 0;

interface SidebarBaseProps {
session: Session | null;
collapsible?: "icon" | "offcanvas" | "none";
Expand All @@ -61,12 +65,27 @@ export function SidebarBase({ session, collapsible = "icon", headerContent, chil
const contentRef = useRef<HTMLDivElement>(null);
const isMobile = useIsMobile();

// The sidebar lives in the `@sidebar` parallel route slot, whose catch-all
// segment remounts on navigation between chats. Persist the scroll position
// across those remounts so it isn't reset to the top each time.
useLayoutEffect(() => {
const el = contentRef.current;
if (!el) {
return;
}
el.scrollTop = lastScrollTop;
setIsScrolled(el.scrollTop > 0);
}, []);

useEffect(() => {
const el = contentRef.current;
if (!el) {
return;
}
const handleScroll = () => setIsScrolled(el.scrollTop > 0);
const handleScroll = () => {
lastScrollTop = el.scrollTop;
setIsScrolled(el.scrollTop > 0);
};
el.addEventListener("scroll", handleScroll);
return () => el.removeEventListener("scroll", handleScroll);
}, []);
Expand Down
Loading