From 9915665e4c519c53418cfd9d0219b85e8c5bfa4d Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Thu, 4 Jun 2026 17:10:05 -0500 Subject: [PATCH] fix(desktop): step profile-rail drags cell-by-cell, clamp to strip Snap the drag transform to whole cells (no free glide) and clamp it to the occupied squares strip via a relative wrapper as offsetParent, so a square can't float past the last profile onto the "+" and break the layout. --- .../src/app/chat/sidebar/profile-switcher.tsx | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx b/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx index 0f3967017..46f135967 100644 --- a/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +++ b/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx @@ -42,9 +42,24 @@ import { import { CreateProfileDialog } from '../../profiles/create-profile-dialog' import { PROFILES_ROUTE } from '../../routes' -// The rail is a single horizontal strip; pin drags to the x-axis so a vertical -// nudge can't translate a square down and fault in a cross-axis scrollbar. -const lockToXAxis: Modifier = ({ transform }) => ({ ...transform, y: 0 }) +const RAIL_GAP = 4 // px — matches gap-1 between squares. + +// The rail is a single horizontal strip of fixed cells. Pin drags to the x-axis +// (no cross-axis scrollbar), snap to whole cells so a square steps slot-to-slot +// instead of gliding, and clamp to the occupied strip so it can't float past the +// last profile onto the "+". +const stepThroughCells: Modifier = ({ containerNodeRect, draggingNodeRect, transform }) => { + if (!draggingNodeRect || !containerNodeRect) { + return { ...transform, y: 0 } + } + + const pitch = draggingNodeRect.width + RAIL_GAP + const minX = containerNodeRect.left - draggingNodeRect.left + const maxX = containerNodeRect.right - draggingNodeRect.right + const snapped = Math.round(transform.x / pitch) * pitch + + return { ...transform, x: Math.min(maxX, Math.max(minX, snapped)), y: 0 } +} // Arc-Spaces-style profile rail at the sidebar foot: a default↔all toggle pinned // left, the colored named profiles scrolling between, and Manage pinned right. @@ -112,20 +127,24 @@ export function ProfileRail() {
profile.name)} strategy={horizontalListSortingStrategy}> - {named.map(profile => ( - selectProfile(profile.name)} - /> - ))} + {/* relative → the strip is the dragged square's offsetParent, so the + clamp modifier bounds drags to the occupied cells (not the +). */} +
+ {named.map(profile => ( + selectProfile(profile.name)} + /> + ))} +