-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat(web): add toggle shortcut for settling threads #7881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0420fb1
6fe4f8a
311be01
d999951
6f592f7
f000b1b
6742b00
ed7f8e4
1d52535
8c56333
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,27 @@ | ||
| import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"; | ||
| import { useAtomValue } from "@effect/atom-react"; | ||
| import { useEffect, useMemo } from "react"; | ||
| import { scopedThreadKey } from "@t3tools/client-runtime/environment"; | ||
| import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled"; | ||
| import { | ||
| isAtomCommandInterrupted, | ||
| squashAtomCommandFailure, | ||
| } from "@t3tools/client-runtime/state/runtime"; | ||
|
|
||
| import { isCommandPaletteOpen } from "../commandPaletteBus"; | ||
| import { useClientSettings, useLegacySidebarEnabled } from "../hooks/useSettings"; | ||
| import { openCommandPalette } from "../commandPaletteBus"; | ||
| import { useProjects } from "../state/entities"; | ||
| import { useProjects, readProject, useThreadShell } from "../state/entities"; | ||
| import { usePrimaryEnvironmentId } from "../state/environments"; | ||
| import { selectProjectGroupingSettings } from "../logicalProject"; | ||
| import { buildSidebarProjectSnapshots } from "../sidebarProjectGrouping"; | ||
| import { | ||
| resolveDisplayedThreadPr, | ||
| threadChangeRequestSnapshotsAtom, | ||
| } from "../components/ThreadStatusIndicators"; | ||
| import { dispatchPreviewAction } from "../components/preview/previewActionBus"; | ||
| import { useHandleNewThread } from "../hooks/useHandleNewThread"; | ||
| import { useThreadActions } from "../hooks/useThreadActions"; | ||
| import { startNewThreadFromContext } from "../lib/chatThreadActions"; | ||
| import { isPreviewFocused } from "../lib/previewFocus"; | ||
| import { isTerminalFocused } from "../lib/terminalFocus"; | ||
|
|
@@ -19,14 +30,17 @@ import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../termina | |
| import { isPreviewSupportedInRuntime } from "../previewStateStore"; | ||
| import { selectActiveRightPanel, useRightPanelStore } from "../rightPanelStore"; | ||
| import { useThreadSelectionStore } from "../threadSelectionStore"; | ||
| import { useEnvironmentQuery } from "../state/query"; | ||
| import { vcsEnvironment } from "../state/vcs"; | ||
| import { stackedThreadToast, toastManager } from "~/components/ui/toast"; | ||
| import { primaryServerKeybindingsAtom } from "~/state/server"; | ||
| import { environmentServerConfigsAtom, primaryServerKeybindingsAtom } from "~/state/server"; | ||
|
|
||
| function ChatRouteGlobalShortcuts() { | ||
| const clearSelection = useThreadSelectionStore((state) => state.clearSelection); | ||
| const selectedThreadKeysSize = useThreadSelectionStore((state) => state.selectedThreadKeys.size); | ||
| const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread, routeThreadRef } = | ||
| useHandleNewThread(); | ||
| const activeThreadShell = useThreadShell(routeThreadRef); | ||
| const keybindings = useAtomValue(primaryServerKeybindingsAtom); | ||
| const legacySidebarEnabled = useLegacySidebarEnabled(); | ||
| const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); | ||
|
|
@@ -55,6 +69,30 @@ function ChatRouteGlobalShortcuts() { | |
| ? selectActiveRightPanel(state.byThreadKey, routeThreadRef) === "preview" | ||
| : false, | ||
| ); | ||
| const { settleThread, unsettleThread } = useThreadActions(); | ||
| const serverConfigs = useAtomValue(environmentServerConfigsAtom); | ||
| const changeRequestSnapshotByKey = useAtomValue(threadChangeRequestSnapshotsAtom); | ||
| const autoSettleAfterDays = useClientSettings((settings) => settings.sidebarAutoSettleAfterDays); | ||
| const autoSettleOnMerge = useClientSettings((settings) => settings.sidebarAutoSettleOnMerge); | ||
| // PR resolution mirrors ChatView's banner exactly: live VCS status first, | ||
| // snapshot second. The snapshot alone (Sidebar-written) is missing on the | ||
| // legacy sidebar or before a row mounts, which would misclassify settle. | ||
| const gitStatusCwd = | ||
| activeThreadShell?.worktreePath ?? | ||
| (routeThreadRef && activeThreadShell | ||
| ? (readProject({ | ||
| environmentId: routeThreadRef.environmentId, | ||
| projectId: activeThreadShell.projectId, | ||
| })?.workspaceRoot ?? null) | ||
| : null); | ||
| const gitStatusQuery = useEnvironmentQuery( | ||
| routeThreadRef === null || gitStatusCwd === null | ||
| ? null | ||
| : vcsEnvironment.status({ | ||
| environmentId: routeThreadRef.environmentId, | ||
| input: { cwd: gitStatusCwd }, | ||
| }), | ||
| ); | ||
| useEffect(() => { | ||
| const onWindowKeyDown = (event: KeyboardEvent) => { | ||
| if (event.defaultPrevented) return; | ||
|
|
@@ -149,6 +187,66 @@ function ChatRouteGlobalShortcuts() { | |
| ? "zoom-out" | ||
| : "reset-zoom"; | ||
| dispatchPreviewAction(action); | ||
| return; | ||
| } | ||
|
|
||
| if (command === "thread.settle.toggle") { | ||
| event.preventDefault(); | ||
| event.stopPropagation(); | ||
| if (event.repeat) return; | ||
| if (!routeThreadRef || !activeThreadShell) return; | ||
| const supportsSettlement = | ||
| serverConfigs.get(routeThreadRef.environmentId)?.environment.capabilities | ||
| .threadSettlement === true; | ||
| if (!supportsSettlement) return; | ||
| const threadKey = scopedThreadKey(routeThreadRef); | ||
| const snapshot = changeRequestSnapshotByKey.get(threadKey); | ||
| // While VCS status is still loading, resolveDisplayedThreadPr drops | ||
| // non-terminal (open) snapshot PRs, which would let inactivity | ||
| // auto-settle classify a thread with an open PR as settled. Use the | ||
| // Sidebar's snapshot rule until the live status lands. | ||
| const activeThreadPr = | ||
| gitStatusQuery.data !== null || !gitStatusQuery.isPending | ||
| ? resolveDisplayedThreadPr({ | ||
| threadBranch: activeThreadShell.branch, | ||
| gitStatus: gitStatusQuery.data, | ||
| snapshot, | ||
| retainTerminalOnBranchMismatch: activeThreadShell.worktreePath === null, | ||
| }) | ||
| : snapshot != null && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium While VCS status is pending, a snapshot from a previous branch is treated as the current PR whenever 🤖 Copy this AI Prompt to have your agent fix this: |
||
| (activeThreadShell.worktreePath === null || | ||
| snapshot.branch === activeThreadShell.branch) | ||
| ? snapshot.pr | ||
| : null; | ||
| const changeRequest = | ||
| activeThreadPr === null | ||
| ? null | ||
| : { state: activeThreadPr.state, updatedAt: activeThreadPr.updatedAt }; | ||
| // Classify like ChatView's parked-thread banner and the header menu: | ||
| // effectiveSettled alone, minute-quantized so it cannot disagree | ||
| // with those surfaces within the same minute. | ||
| const isSettled = effectiveSettled(activeThreadShell, { | ||
| now: `${new Date().toISOString().slice(0, 16)}:00.000Z`, | ||
| autoSettleAfterDays, | ||
| autoSettleOnMerge, | ||
| changeRequest, | ||
| }); | ||
|
macroscopeapp[bot] marked this conversation as resolved.
|
||
| void (async () => { | ||
| const result = isSettled | ||
| ? await unsettleThread(routeThreadRef) | ||
| : await settleThread(routeThreadRef); | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { | ||
| const error = squashAtomCommandFailure(result); | ||
| toastManager.add( | ||
| stackedThreadToast({ | ||
| type: "error", | ||
| title: isSettled ? "Failed to un-settle thread" : "Failed to settle thread", | ||
| description: error instanceof Error ? error.message : "An error occurred.", | ||
| }), | ||
| ); | ||
| } | ||
| })(); | ||
| return; | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -159,16 +257,24 @@ function ChatRouteGlobalShortcuts() { | |
| }, [ | ||
| activeDraftThread, | ||
| activeThread, | ||
| activeThreadShell, | ||
| autoSettleAfterDays, | ||
| autoSettleOnMerge, | ||
| changeRequestSnapshotByKey, | ||
| clearSelection, | ||
| handleNewThread, | ||
| gitStatusQuery.data, | ||
| keybindings, | ||
| defaultProjectRef, | ||
| previewOpen, | ||
| projectGroupCount, | ||
| routeThreadRef, | ||
| selectedThreadKeysSize, | ||
| legacySidebarEnabled, | ||
| serverConfigs, | ||
| settleThread, | ||
| terminalOpen, | ||
| unsettleThread, | ||
| ]); | ||
|
|
||
| return null; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.