diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index 9a51373cf..c684689fb 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -161,7 +161,7 @@ const config: ExpoConfig = { slug: "t3-code", platforms: ["ios", "android"], scheme: variant.scheme, - version: "1.0.1", + version: "1.0.2", runtimeVersion: { // Fingerprint (not appVersion) so an OTA only reaches binaries whose native // project — native deps, config plugins, AND patches/ — matches the update. diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index 590ca9cb5..81d883508 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 @@ -2790,11 +2816,13 @@ export default function SidebarV2() { openCommandPalette({ open: "new-thread-in" }); }, [isMobile, newThreadContext, projectGroups.length, setOpenMobile]); - // Same resolution as v1: prefer the local-thread binding, fall back to - // chat.new, no platform gating — web users have working shortcuts too. + // The button mirrors chat.new: in multi-project setups both route through + // the command palette's "New thread in..." picker, and in single-project + // setups both create immediately. chat.newLocal always creates directly, so + // it is only a correct label when chat.new is unbound. const newThreadShortcutLabel = - shortcutLabelForCommand(keybindings, "chat.newLocal") ?? - shortcutLabelForCommand(keybindings, "chat.new"); + shortcutLabelForCommand(keybindings, "chat.new") ?? + shortcutLabelForCommand(keybindings, "chat.newLocal"); return ( <> @@ -3080,6 +3108,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 +3152,7 @@ export default function SidebarV2() { onUnsettle={attemptUnsettle} onSnooze={attemptSnooze} onUnsnooze={attemptUnsnooze} + onUnpin={attemptUnpin} onAcknowledgeWoke={acknowledgeWoke} onChangeRequestState={handleChangeRequestState} />