From 7f182a74ddb9c00c21bcab5777d7f15484531459 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 4 Sep 2026 11:35:52 -0700 Subject: [PATCH 1/2] feat(web): simplify sidebar drag destination cues --- apps/web/src/components/Sidebar.tsx | 87 ++++++++++++----------------- docs/user/thread-sidebar.md | 7 ++- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index be01afb5569..e594b6a8ce7 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -568,7 +568,6 @@ function SidebarSectionPlaceholder(props: { function SidebarDragBoundary(props: { marker: "pinned-header" | "pinned-divider"; label: string; - hint: string | null; visible: boolean; isDropTarget: boolean; }) { @@ -587,7 +586,6 @@ function SidebarDragBoundary(props: { )} > {props.label} - {props.hint ? {props.hint} : null} void }; }) { @@ -624,7 +621,6 @@ function SidebarSectionHeader(props: { props.isDropTarget && "bg-primary/30", )} /> - {props.hint ? {props.hint} : null} | null; // Compact wake countdown ("2h") for rows in the snoozed shelf. snoozeWakeLabelText: string | null; // When a snooze ended (timer or early wake); drives the Woke pill until @@ -1374,14 +1370,13 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { role="status" className="pointer-events-none ml-auto inline-flex h-5 shrink-0 items-center gap-1 rounded-sm border border-primary/30 bg-sidebar px-1.5 text-[11px] font-medium text-primary" > + Move to {props.dropSection === "pinned" ? "Pinned" : props.dropSection === "active" ? "Active" - : props.dropSection === "settled" - ? "Settled" - : "Snoozed"} + : "Settled"} ) : null; @@ -1485,31 +1480,32 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { Unsent draft ) : null; - const pinIndicator = props.isPinned ? ( - props.pinningSupported ? ( - - - } - > - - - Unpin thread - - ) : ( - - ) - ) : null; + const pinIndicator = + props.isPinned && !sortable?.isDragging ? ( + props.pinningSupported ? ( + + + } + > + + + Unpin thread + + ) : ( + + ) + ) : null; if (variant === "slim") { return ( @@ -4569,7 +4565,11 @@ export default function Sidebar() { isPinned={thread.pinnedAt != null} sortable={sortable} dropSection={ - dragState?.activeKey === threadKey ? dragTargetSection : null + dragState?.activeKey === threadKey && + dragTargetSection !== dragState.activeSection && + dragTargetSection !== "snoozed" + ? dragTargetSection + : null } snoozeWakeLabelText={ section === "snoozed" && thread.snoozedUntil != null @@ -4673,14 +4673,6 @@ export default function Sidebar() { dragTargetSection !== "pinned" ? 1 : 0); - const activeHint = - from === "pinned" - ? "Drop to unpin" - : from === "settled" - ? "Drop to un-settle" - : from === "snoozed" - ? "Drop to wake" - : null; const items: ReactNode[] = [ , @@ -4718,7 +4709,6 @@ export default function Sidebar() { key="pinned-divider" marker="pinned-divider" label="Active" - hint={dragTargetSection === "active" ? activeHint : null} isDropTarget={dragTargetSection === "active"} visible={from !== null && previewPinnedCount > 0} />, @@ -4729,7 +4719,7 @@ export default function Sidebar() { , @@ -4762,11 +4752,6 @@ export default function Sidebar() { ? "Settled" : `Settled (${settledThreads.length})` } - hint={ - dragTargetSection === "settled" && from !== "settled" - ? "Drop to settle" - : null - } isDropTarget={dragTargetSection === "settled"} toggle={{ expanded: settledShelfExpanded, @@ -4780,7 +4765,7 @@ export default function Sidebar() { , diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md index e2604414270..a18f6dc974d 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -33,9 +33,10 @@ thread into the active list un-settles it. A snoozed thread can be dragged out o shelf, which wakes it, but threads cannot be dragged into the shelf because snoozing needs a wake time. Dragging a pinned thread out of the pinned section does not ask for unpin confirmation. Pinned and active boundary labels appear only while dragging, without moving the rows. The -destination boundary highlights and the thread shows which section it will land in. When there -are no pins, drag to the top edge to pin a thread. Drop instructions also appear for empty sections -and a collapsed settled shelf. +destination boundary highlights. When you cross into another section, the dragged thread shows +its destination, such as **→ Active**. Its pin marker hides during the drag. Reordering within +the same section does not show a destination badge. When there are no pins, drag to the top edge +to pin a thread. Section labels also identify empty sections and a collapsed settled shelf. Drag within the pinned or active section to change its order. Other rows slide aside to show the spot where the thread will land. Drops into either section keep the position you choose. On From 84346fbb527b868fa8d0ff40ab652b7b3851edc0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 4 Sep 2026 11:46:49 -0700 Subject: [PATCH 2/2] fix(web): hide row actions while dragging --- apps/web/src/components/Sidebar.tsx | 8 ++++++-- docs/user/thread-sidebar.md | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index e594b6a8ce7..fc7b4b332b4 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -1565,7 +1565,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { remain visible AND clickable while the row is hovered. Only the time/jump label yields to the settle affordance. */} {prBadge} - {dragDestination ?? ( + {sortable?.isDragging ? ( + dragDestination + ) : (