feat(plan-sidebar): add drag-to-resize with localStorage persistence#1438
feat(plan-sidebar): add drag-to-resize with localStorage persistence#1438mia-riezebos wants to merge 2 commits intopingdotgg:mainfrom
Conversation
The Plan sidebar was hardcoded at 340px with no way to resize it. Now it has a proper resize handle on its left edge — drag to widen or narrow between 240–560px. Width persists across close/reopen and page refresh via localStorage. Follows the same pointer-capture pattern as ThreadTerminalDrawer: pointerDown captures, pointerMove updates width in real-time, pointerEnd persists to storage. The resize hit-area lives fully inside the sidebar bounds so it never overlaps the chat scroll viewport — a lesson learned from pingdotgg#958. Closes pingdotgg#958 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PlanSidebar is conditionally rendered — if the component unmounts mid-drag (user closes the sidebar while resizing), the body cursor and user-select overrides would leak permanently. Added a useEffect cleanup that unconditionally removes them, matching SidebarRail's existing cleanup pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| function readStoredWidth(): number { | ||
| const stored = getLocalStorageItem(PLAN_SIDEBAR_WIDTH_STORAGE_KEY, Schema.Finite); | ||
| return stored !== null ? clampSidebarWidth(stored) : PLAN_SIDEBAR_DEFAULT_WIDTH; | ||
| } |
There was a problem hiding this comment.
Unhandled decode error in state initializer crashes component
Low Severity
readStoredWidth calls getLocalStorageItem without a try-catch. Since Schema.decodeSync throws on corrupted/invalid localStorage data, and this function runs inside a useState lazy initializer, a decode failure will crash the entire component render. The useLocalStorage hook wraps the same call in try-catch for this exact reason. In contrast, sidebar.tsx calls getLocalStorageItem inside a useEffect where a throw is less catastrophic. Here, it prevents the sidebar from mounting at all.
|
incorrect commit messages, retargeting this pr. |


What Changed
Adds drag-to-resize to the Plan sidebar and follows up with cleanup for drag styles if the sidebar unmounts mid-resize.
w-[340px]layout with a dynamic width driven by React statePlanSidebarlocalStoragedocument.bodycursor anduser-selectoverrides if the sidebar unmounts during a dragWhy
The Plan sidebar was fixed at 340px with no way to adjust it for different screen sizes or content widths.
This implementation keeps the resize handle fully inside the Plan sidebar bounds so it does not steal pointer interactions from the chat area. That is the same class of interaction problem described in #958, but this PR does not close #958 because that issue is specifically about the diff sidebar /
SidebarRail.UI Changes
This is a drag-interaction change. The resize handle is invisible by default and shows a 2px border indicator on hover, consistent with the existing resize affordances.
Before:
Screen.Recording.2026-03-26.at.14.37.34_compressed.mp4
After:
Screen.Recording.2026-03-26.at.14.39.16_compressed.mp4
Checklist
Note
Low Risk
Low risk UI-only change confined to
PlanSidebarthat adds pointer-driven resizing and localStorage persistence; main risk is minor UX regressions (cursor/user-select leakage) during drag interactions.Overview
Makes the Plan sidebar width user-resizable by replacing the fixed
w-[340px]layout with a state-driven inline width, clamped between 240–560px.Adds a left-edge pointer-capture resize handle and persists the final width to
localStorage(Schema.Finite) on drag end, with unmount cleanup to avoid leakedcursor/user-selectbody styles.Written by Cursor Bugbot for commit 6e60894. This will update automatically on new commits. Configure here.
Note
Add drag-to-resize with localStorage persistence to
PlanSidebarPlanSidebarthat lets users resize the sidebar between 240px and 560px (default 340px).plan-sidebar-widthusingSchema.Finitevalidation and restored on load.user-selectare mutated during drag and cleaned up on release or unmount.<a href="https://app.macroscope.com\">Macroscope summarized 6e60894.