Skip to content
Open
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
64 changes: 58 additions & 6 deletions apps/web/src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ type SidebarResizableOptions = {
storageKey?: string;
};

const SidebarStoredWidthSchema = Schema.Union([
Schema.Finite,
Schema.Struct({
unit: Schema.Literal("fraction"),
value: Schema.Finite,
}),
]);

type SidebarStoredWidth = typeof SidebarStoredWidthSchema.Type;

type SidebarResolvedResizableOptions = {
maxWidth: number;
minWidth: number;
Expand Down Expand Up @@ -335,6 +345,26 @@ function clampSidebarWidth(width: number, options: SidebarResolvedResizableOptio
return Math.max(options.minWidth, Math.min(width, options.maxWidth));
}

function getSidebarWidthStorageBasis(wrapper: HTMLElement): number {
return wrapper.parentElement?.clientWidth || window.innerWidth;
}

function getStoredSidebarWidth(
storedWidth: SidebarStoredWidth,
wrapper: HTMLElement,
): number | null {
if (typeof storedWidth === "number") {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy stored widths ignored

Medium Severity

SidebarStoredWidthSchema still accepts legacy numeric pixel values from localStorage, but getStoredSidebarWidth returns null for numbers so applyStoredWidth never restores them. Existing saved sidebar widths are silently dropped until the user drags the rail again.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ce3f903. Configure here.


return storedWidth.value * getSidebarWidthStorageBasis(wrapper);
}

function createStoredSidebarWidth(width: number, wrapper: HTMLElement): SidebarStoredWidth {
const basis = getSidebarWidthStorageBasis(wrapper);
return { unit: "fraction", value: basis > 0 ? width / basis : 0 };
}

function SidebarRail({
className,
onClick,
Expand Down Expand Up @@ -380,7 +410,11 @@ function SidebarRail({
element.style.removeProperty("transition-duration");
});
if (resolvedResizable?.storageKey && typeof window !== "undefined") {
setLocalStorageItem(resolvedResizable.storageKey, resizeState.width, Schema.Finite);
setLocalStorageItem(
resolvedResizable.storageKey,
createStoredSidebarWidth(resizeState.width, resizeState.wrapper),
SidebarStoredWidthSchema,
);
}
resolvedResizable?.onResize?.(resizeState.width);
resizeStateRef.current = null;
Expand Down Expand Up @@ -545,16 +579,34 @@ function SidebarRail({

React.useEffect(() => {
if (!resolvedResizable?.storageKey || typeof window === "undefined") return;
const storageKey = resolvedResizable.storageKey;
const rail = railRef.current;
if (!rail) return;
const wrapper = rail.closest<HTMLElement>("[data-slot='sidebar-wrapper']");
if (!wrapper) return;
const storageBasisElement = wrapper.parentElement;

const applyStoredWidth = () => {
const storedWidth = getLocalStorageItem(storageKey, SidebarStoredWidthSchema);
if (storedWidth === null) return;
const width = getStoredSidebarWidth(storedWidth, wrapper);
if (width === null) return;
const clampedWidth = clampSidebarWidth(width, resolvedResizable);
wrapper.style.setProperty("--sidebar-width", `${clampedWidth}px`);
resolvedResizable.onResize?.(clampedWidth);
};

const storedWidth = getLocalStorageItem(resolvedResizable.storageKey, Schema.Finite);
if (storedWidth === null) return;
const clampedWidth = clampSidebarWidth(storedWidth, resolvedResizable);
wrapper.style.setProperty("--sidebar-width", `${clampedWidth}px`);
resolvedResizable.onResize?.(clampedWidth);
applyStoredWidth();
window.addEventListener("resize", applyStoredWidth);
const resizeObserver = new ResizeObserver(applyStoredWidth);
if (storageBasisElement) {
resizeObserver.observe(storageBasisElement);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resize reapplies stale storage

Medium Severity

While the user is dragging the sidebar rail, window resize and the parent ResizeObserver call applyStoredWidth, which reads the last persisted fraction from localStorage—not the in-progress drag width—and sets --sidebar-width. That can snap the sidebar back mid-drag when the window or container resizes.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ce3f903. Configure here.


return () => {
window.removeEventListener("resize", applyStoredWidth);
resizeObserver?.disconnect();
};
}, [resolvedResizable]);

React.useEffect(() => {
Expand Down
Loading