From c930dc75cf24d09f1c0bece27bf8721f5d83e0f7 Mon Sep 17 00:00:00 2001 From: UtkarshUsername Date: Fri, 7 Aug 2026 12:31:54 +0530 Subject: [PATCH 1/3] feat(web): click the pin icon to unpin a thread --- apps/web/src/components/SidebarV2.tsx | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index 003bec64d0f..342aa103e52 100644 --- a/apps/web/src/components/SidebarV2.tsx +++ b/apps/web/src/components/SidebarV2.tsx @@ -402,8 +402,9 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { snoozeSupported: boolean; // Renders the pin glyph. Pinned cards keep the full settle/snooze quick // actions: settling clears the pin server-side, and snoozing hides the - // card until wake with the pin intact underneath. Pin/unpin themselves - // live in the context menu only. + // card until wake with the pin intact underneath. The pin glyph itself + // is a quick-action that unpins in place; pinning lives in the context + // menu. isPinned: boolean; // Compact wake countdown ("2h") for rows in the snoozed shelf. snoozeWakeLabelText: string | null; @@ -431,6 +432,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { onUnsettle: (threadRef: ScopedThreadRef) => void; onSnooze: (threadRef: ScopedThreadRef, preset: SnoozePreset) => void; onUnsnooze: (threadRef: ScopedThreadRef) => void; + onUnpin: (threadRef: ScopedThreadRef) => void; onAcknowledgeWoke: (threadRef: ScopedThreadRef, visitedAt: string) => void; onChangeRequestState: (threadKey: string, state: "open" | "closed" | "merged" | null) => void; }) { @@ -449,6 +451,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { onThreadClick, onUnsettle, onUnsnooze, + onUnpin, renamingTitle, thread, variant, @@ -692,6 +695,14 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { }, [onUnsnooze, threadRef], ); + const handleUnpinClick = useCallback( + (event: ReactMouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onUnpin(threadRef); + }, + [onUnpin, threadRef], + ); const handleSnoozePreset = useCallback( (preset: SnoozePreset) => { onSnooze(threadRef, preset); @@ -989,11 +1000,15 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { )} {props.isPinned ? ( - + ) : null} {/* The visible state owns this slot's width: status at rest, actions on hover/keyboard focus or while the popover is open. Keeping @@ -3136,6 +3151,7 @@ export default function SidebarV2() { onUnsettle={attemptUnsettle} onSnooze={attemptSnooze} onUnsnooze={attemptUnsnooze} + onUnpin={attemptUnpin} onAcknowledgeWoke={acknowledgeWoke} onChangeRequestState={handleChangeRequestState} /> From 8ee2f082f17e491ad76d7f0a3cea025634c1aa14 Mon Sep 17 00:00:00 2001 From: UtkarshUsername Date: Fri, 7 Aug 2026 13:24:41 +0530 Subject: [PATCH 2/3] fix(web): gate the unpin quick action on the threadPinning capability --- apps/web/src/components/SidebarV2.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index a8d0ad9afd1..0c96916bed9 100644 --- a/apps/web/src/components/SidebarV2.tsx +++ b/apps/web/src/components/SidebarV2.tsx @@ -404,7 +404,9 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { // actions: settling clears the pin server-side, and snoozing hides the // card until wake with the pin intact underneath. The pin glyph itself // is a quick-action that unpins in place; pinning lives in the context - // menu. + // menu. Like the other lifecycle affordances it is capability-gated so + // it hides rather than fails while the descriptor is not loaded. + pinningSupported: boolean; isPinned: boolean; // Compact wake countdown ("2h") for rows in the snoozed shelf. snoozeWakeLabelText: string | null; @@ -999,7 +1001,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { ) : ( )} - {props.isPinned ? ( + {props.isPinned && props.pinningSupported ? ( + {props.isPinned ? ( + props.pinningSupported ? ( + + ) : ( + + ) ) : null} {/* The visible state owns this slot's width: status at rest, actions on hover/keyboard focus or while the popover is open. Keeping