diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx
index 590ca9cb583..409d6bef9e4 100644
--- a/apps/web/src/components/SidebarV2.tsx
+++ b/apps/web/src/components/SidebarV2.tsx
@@ -403,8 +403,12 @@ 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 glyph is also the
+ // in-row pin state cue (the pinned block has no header), so it always
+ // shows while pinned; it only becomes a clickable unpin quick-action once
+ // the pinning capability is confirmed, and stays a passive marker while
+ // the descriptor is not loaded. Pinning itself lives in the context menu.
+ pinningSupported: boolean;
isPinned: boolean;
// Compact wake countdown ("2h") for rows in the snoozed shelf.
snoozeWakeLabelText: string | null;
@@ -432,6 +436,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;
}) {
@@ -450,6 +455,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
onThreadClick,
onUnsettle,
onUnsnooze,
+ onUnpin,
renamingTitle,
thread,
variant,
@@ -704,6 +710,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);
@@ -1001,11 +1015,23 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
)}
{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
@@ -3080,6 +3106,10 @@ export default function SidebarV2() {
serverConfigs.get(thread.environmentId)?.environment.capabilities
.threadSnooze === true
}
+ pinningSupported={
+ serverConfigs.get(thread.environmentId)?.environment.capabilities
+ .threadPinning === true
+ }
isPinned={section === "pinned"}
snoozeWakeLabelText={
section === "snoozed" && thread.snoozedUntil != null
@@ -3120,6 +3150,7 @@ export default function SidebarV2() {
onUnsettle={attemptUnsettle}
onSnooze={attemptSnooze}
onUnsnooze={attemptUnsnooze}
+ onUnpin={attemptUnpin}
onAcknowledgeWoke={acknowledgeWoke}
onChangeRequestState={handleChangeRequestState}
/>