authorgravatar for 73941998+UtkarshUsername@users.noreply.github.comUtkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> 2026-08-07 16:24:11+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-07 12:54:11+02:00
log61b51ae0e343afc42ba0424d4d74b4f9a216d620
treeb0119c2fdb42e119fe2f2f5186f4f2b9c7c4c260
parentbfc69e4b4de12cd9867972dc802936acfb219b74
signature Signed by PGP key B5690EEEBB952194

feat(web): click the pin icon to unpin a thread (#5578)


1 files changed, 38 insertions(+), 7 deletions(-)

apps/web/src/components/SidebarV2.tsx+38-7
...@@ -403,8 +403,12 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -403,8 +403,12 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
403 snoozeSupported: boolean;403 snoozeSupported: boolean;
404 // Renders the pin glyph. Pinned cards keep the full settle/snooze quick404 // Renders the pin glyph. Pinned cards keep the full settle/snooze quick
405 // actions: settling clears the pin server-side, and snoozing hides the405 // actions: settling clears the pin server-side, and snoozing hides the
406 // card until wake with the pin intact underneath. Pin/unpin themselves406 // card until wake with the pin intact underneath. The glyph is also the
407 // live in the context menu only.407 // in-row pin state cue (the pinned block has no header), so it always
408 // shows while pinned; it only becomes a clickable unpin quick-action once
409 // the pinning capability is confirmed, and stays a passive marker while
410 // the descriptor is not loaded. Pinning itself lives in the context menu.
411 pinningSupported: boolean;
408 isPinned: boolean;412 isPinned: boolean;
409 // Compact wake countdown ("2h") for rows in the snoozed shelf.413 // Compact wake countdown ("2h") for rows in the snoozed shelf.
410 snoozeWakeLabelText: string | null;414 snoozeWakeLabelText: string | null;
...@@ -432,6 +436,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -432,6 +436,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
432 onUnsettle: (threadRef: ScopedThreadRef) => void;436 onUnsettle: (threadRef: ScopedThreadRef) => void;
433 onSnooze: (threadRef: ScopedThreadRef, preset: SnoozePreset) => void;437 onSnooze: (threadRef: ScopedThreadRef, preset: SnoozePreset) => void;
434 onUnsnooze: (threadRef: ScopedThreadRef) => void;438 onUnsnooze: (threadRef: ScopedThreadRef) => void;
439 onUnpin: (threadRef: ScopedThreadRef) => void;
435 onAcknowledgeWoke: (threadRef: ScopedThreadRef, visitedAt: string) => void;440 onAcknowledgeWoke: (threadRef: ScopedThreadRef, visitedAt: string) => void;
436 onChangeRequestState: (threadKey: string, state: "open" | "closed" | "merged" | null) => void;441 onChangeRequestState: (threadKey: string, state: "open" | "closed" | "merged" | null) => void;
437}) {442}) {
...@@ -450,6 +455,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -450,6 +455,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
450 onThreadClick,455 onThreadClick,
451 onUnsettle,456 onUnsettle,
452 onUnsnooze,457 onUnsnooze,
458 onUnpin,
453 renamingTitle,459 renamingTitle,
454 thread,460 thread,
455 variant,461 variant,
...@@ -704,6 +710,14 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -704,6 +710,14 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
704 },710 },
705 [onUnsnooze, threadRef],711 [onUnsnooze, threadRef],
706 );712 );
713 const handleUnpinClick = useCallback(
714 (event: ReactMouseEvent) => {
715 event.preventDefault();
716 event.stopPropagation();
717 onUnpin(threadRef);
718 },
719 [onUnpin, threadRef],
720 );
707 const handleSnoozePreset = useCallback(721 const handleSnoozePreset = useCallback(
708 (preset: SnoozePreset) => {722 (preset: SnoozePreset) => {
709 onSnooze(threadRef, preset);723 onSnooze(threadRef, preset);
...@@ -1001,11 +1015,23 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -1001,11 +1015,23 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
1001 <span className="flex-1" />1015 <span className="flex-1" />
1002 )}1016 )}
1003 {props.isPinned ? (1017 {props.isPinned ? (
1004 <PinIcon1018 props.pinningSupported ? (
1005 aria-label="Pinned"1019 <button
1006 role="img"1020 type="button"
1007 className="size-3 shrink-0 text-muted-foreground/65"1021 aria-label="Unpin thread"
1008 />1022 title="Unpin thread"
1023 onClick={handleUnpinClick}
1024 className="inline-flex cursor-pointer items-center rounded-sm text-muted-foreground/65 outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
1025 >
1026 <PinIcon aria-hidden className="size-3 shrink-0" />
1027 </button>
1028 ) : (
1029 <PinIcon
1030 aria-label="Pinned"
1031 role="img"
1032 className="size-3 shrink-0 text-muted-foreground/65"
1033 />
1034 )
1009 ) : null}1035 ) : null}
1010 {/* The visible state owns this slot's width: status at rest,1036 {/* The visible state owns this slot's width: status at rest,
1011 actions on hover/keyboard focus or while the popover is open. Keeping1037 actions on hover/keyboard focus or while the popover is open. Keeping
...@@ -3080,6 +3106,10 @@ export default function SidebarV2() {...@@ -3080,6 +3106,10 @@ export default function SidebarV2() {
3080 serverConfigs.get(thread.environmentId)?.environment.capabilities3106 serverConfigs.get(thread.environmentId)?.environment.capabilities
3081 .threadSnooze === true3107 .threadSnooze === true
3082 }3108 }
3109 pinningSupported={
3110 serverConfigs.get(thread.environmentId)?.environment.capabilities
3111 .threadPinning === true
3112 }
3083 isPinned={section === "pinned"}3113 isPinned={section === "pinned"}
3084 snoozeWakeLabelText={3114 snoozeWakeLabelText={
3085 section === "snoozed" && thread.snoozedUntil != null3115 section === "snoozed" && thread.snoozedUntil != null
...@@ -3120,6 +3150,7 @@ export default function SidebarV2() {...@@ -3120,6 +3150,7 @@ export default function SidebarV2() {
3120 onUnsettle={attemptUnsettle}3150 onUnsettle={attemptUnsettle}
3121 onSnooze={attemptSnooze}3151 onSnooze={attemptSnooze}
3122 onUnsnooze={attemptUnsnooze}3152 onUnsnooze={attemptUnsnooze}
3153 onUnpin={attemptUnpin}
3123 onAcknowledgeWoke={acknowledgeWoke}3154 onAcknowledgeWoke={acknowledgeWoke}
3124 onChangeRequestState={handleChangeRequestState}3155 onChangeRequestState={handleChangeRequestState}
3125 />3156 />