Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { type TerminalLaunchContext, useChatTerminalActions } from "./chat/useCh
import { useComposerSend } from "./chat/useComposerSend";
import { useComposerResume } from "./chat/useComposerResume";
import { useImplementPlanInNewThread } from "./chat/useProposedPlanSend";
import { ThreadFind, ThreadFindCanvas, type ThreadFindControls } from "./chat/ThreadFindProvider";
import { ThreadFind, ThreadFindCanvas } from "./chat/ThreadFindProvider";
import { closeThreadFind, openThreadFind, useThreadFindOpen } from "./chat/threadFindStore";
import { THREAD_FIND_BAR_RESERVED_HEIGHT } from "./chat/ThreadFindBar";
import { usageLimitRecoveryBannerItem } from "./chat/UsageLimitRecoveryBanner";
import { resolveDraftHeroState } from "./ChatView.logic";
Expand Down Expand Up @@ -2680,10 +2681,9 @@ export default function ChatView(props: ChatViewProps) {
const timelineSkills = activeProviderStatus
? resolveProviderSkillsForCwd(activeProviderStatus, gitCwd)
: EMPTY_PROVIDER_SKILLS;
const threadFindControlsRef = useRef<ThreadFindControls | null>(null);
const [isThreadFindActive, setIsThreadFindActive] = useState(false);
const openThreadFind = () => threadFindControlsRef.current?.open();
const closeThreadFind = () => threadFindControlsRef.current?.close();
const threadFindEnabled =
isServerThread && serverConfig?.threadFind === true && !paintOnlyDisplayedTimeline;
const isThreadFindActive = useThreadFindOpen(activeThreadRef, threadFindEnabled);

useChatShortcuts({
enabled: activeThreadId !== null && !backgroundThread,
Expand All @@ -2705,8 +2705,9 @@ export default function ChatView(props: ChatViewProps) {
if (!event.repeat) copyActiveThreadReference();
},
"chat.find": () => {
if (!isServerThread || serverConfig?.threadFind !== true) return false;
openThreadFind();
if (!isServerThread || serverConfig?.threadFind !== true || activeThreadKey === null)
return false;
openThreadFind(activeThreadKey);
},
"composer.cycleHost": ({ event }) => {
if (!canChangeEnvironment) return false;
Expand Down Expand Up @@ -3233,11 +3234,8 @@ export default function ChatView(props: ChatViewProps) {
skills: timelineSkills,
progressive: serverConfig?.threadFindProgressive === true,
thread: activeThreadRef,
enabled:
isServerThread && serverConfig?.threadFind === true && !paintOnlyDisplayedTimeline,
enabled: threadFindEnabled,
}}
controlsRef={threadFindControlsRef}
onOpenChange={setIsThreadFindActive}
detailsCardTopInset={isThreadFindActive ? THREAD_FIND_BAR_RESERVED_HEIGHT : 0}
composerOverlayElement={isDraftHeroState ? null : composerOverlayElement}
data-chat-workspace-drop-target="true"
Expand Down
26 changes: 19 additions & 7 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { threadPullRequestLinkMode } from "@t3tools/client-runtime/thread-pull-request-compatibility";
import { visibleThreadPullRequests } from "@t3tools/shared/threadPullRequests";

import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime/environment";
import {
scopedThreadKey,
scopeProjectRef,
scopeThreadRef,
} from "@t3tools/client-runtime/environment";
import {
canCreateProjectInEnvironment,
getCloneDestinationBrowsePath,
Expand Down Expand Up @@ -66,7 +70,7 @@ import {
TextSearchIcon,
UserPlusIcon,
} from "lucide-react";
import { requestThreadFindOpen } from "./chat/threadFindActionBus";
import { openThreadFind } from "./chat/threadFindStore";
import {
useDeferredValue,
useEffect,
Expand Down Expand Up @@ -729,7 +733,9 @@ function OpenCommandPaletteDialog(props: {

const { projectCwdById, projectByKey, projectTitleById } = useProjectLookups();

const activeThreadId = activeThread?.id;
const activeThreadKey = activeThread
? scopedThreadKey(scopeThreadRef(activeThread.environmentId, activeThread.id))
: null;
const supportsThreadFind =
environments.find((environment) => environment.environmentId === activeThread?.environmentId)
?.serverConfig?.threadFind === true;
Expand Down Expand Up @@ -1256,7 +1262,7 @@ function OpenCommandPaletteDialog(props: {
});
}

if (activeThreadId && supportsThreadFind) {
if (activeThreadKey !== null && supportsThreadFind) {
actionItems.push({
kind: "action",
value: "find-current-thread",
Expand All @@ -1265,7 +1271,7 @@ function OpenCommandPaletteDialog(props: {
icon: <TextSearchIcon className={ITEM_ICON_CLASS} />,
shortcutCommand: "chat.find",
run: async () => {
requestThreadFindOpen();
openThreadFind(activeThreadKey);
},
});
}
Expand Down Expand Up @@ -2411,11 +2417,17 @@ function OpenCommandPaletteDialog(props: {
return;
}
// ChatView ignores shortcuts while the palette is open, so intercept find here.
if (command === "chat.find" && activeThreadId && !event.nativeEvent.isComposing) {
if (command === "chat.find" && activeThreadKey !== null && !event.nativeEvent.isComposing) {
event.preventDefault();
event.stopPropagation();
setOpen(false);
requestThreadFindOpen();
if (supportsThreadFind) openThreadFind(activeThreadKey);
else
toastManager.add({
id: "thread-find-unavailable",
title: "Thread search is unavailable on this server.",
description: "Update the server to enable it.",
});
return;
}

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/CommandPaletteThreadItems.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime/environment";
import { findForMessageMatch, requestThreadFindQuery } from "./chat/threadFindActionBus";
import { findForMessageMatch, openThreadFindOnQuery } from "./chat/threadFindStore";
import {
claimThreadOpen,
nextShownOrder,
Expand Down Expand Up @@ -218,7 +218,7 @@ export function useCommandPaletteThreadItems(input: {
// Opens the thread with find on the word at the matched message, so it is in view.
run: async () => {
await item.run();
requestThreadFindQuery(
openThreadFindOnQuery(
scopedThreadKey(scopeThreadRef(match.environmentId, match.threadId)),
findForMessageMatch(match, messageSearch.query),
);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
type ShownOrder,
} from "@t3tools/client-runtime/state/thread-search";
import { foldSearchQuery, rankMessageMatches } from "@t3tools/shared/paletteSearch";
import { findForMessageMatch, requestThreadFindQuery } from "./chat/threadFindActionBus";
import { findForMessageMatch, openThreadFindOnQuery } from "./chat/threadFindStore";
import {
threadRuntimeCanArchive,
type EnvironmentThreadShell,
Expand Down Expand Up @@ -1305,7 +1305,7 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
const query = threadSearch.query;
clearThreadSearch();
navigateToThread(ref);
if (match) requestThreadFindQuery(scopedThreadKey(ref), findForMessageMatch(match, query));
if (match) openThreadFindOnQuery(scopedThreadKey(ref), findForMessageMatch(match, query));
};
const handleThreadSearchKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {
if (event.nativeEvent.isComposing || event.keyCode === 229) return;
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ import {
} from "./MessagesTimeline.logic";
import { type ThreadFindMatch } from "./threadFind";
import { useThreadFindNavigation } from "./useThreadFindNavigation";
import type { ThreadFindPositionReader } from "./threadFind";
import { TooltipScrollDismissArea } from "../ui/tooltip";
import { WorktreeSetupCard } from "./WorktreeSetupCard";
import { useOpenPrLink } from "~/lib/openPullRequestLink";
Expand Down Expand Up @@ -267,7 +266,6 @@ interface MessagesTimelineProps {
topFadeEnabled?: boolean;
historyControls?: MessagesTimelineHistoryControls | undefined;
loadEarlier?: CitationHistoryPage | null;
findPositionReaderRef?: React.RefObject<ThreadFindPositionReader | null>;
findExpanded?: boolean;
findQuery?: string;
activeFindMatch?: ThreadFindMatch | null;
Expand Down Expand Up @@ -315,7 +313,6 @@ const ConversationTimeline = memo(function ConversationTimeline({
findQuery = "",
activeFindMatch = null,
findNavigationId = 0,
findPositionReaderRef,
latestRun,
announcedRun = null,
detailBehindShell = false,
Expand Down Expand Up @@ -1024,7 +1021,7 @@ const ConversationTimeline = memo(function ConversationTimeline({
};

useThreadFindPositionReader({
findPositionReaderRef,
threadKey: listIdentityKey,
timelineViewportElement,
findFoldRunIds,
timelineEntries,
Expand Down
30 changes: 3 additions & 27 deletions apps/web/src/components/chat/ThreadFindProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,31 @@
import {
createContext,
use,
useImperativeHandle,
useLayoutEffect,
type ReactNode,
type ComponentProps,
type Ref,
} from "react";
import { createContext, use, type ReactNode, type ComponentProps } from "react";
import { useThreadFind } from "./useThreadFind";
import { ThreadFindBar } from "./ThreadFindBar";
import { ChatCanvas } from "./ChatCanvas";

type Find = ReturnType<typeof useThreadFind>;
export type ThreadFindControls = Pick<Find, "open" | "close">;
export const ThreadFindTimelineContext = createContext<Find["timelineProps"] | null>(null);
const ThreadFindBarContext = createContext<Find["barProps"] | null>(null);

export function ThreadFindCanvas({
findOptions,
controlsRef,
onOpenChange,
...canvasProps
}: ComponentProps<typeof ChatCanvas> & {
findOptions: Parameters<typeof useThreadFind>[0];
controlsRef: Ref<ThreadFindControls>;
onOpenChange: (open: boolean) => void;
}) {
return (
<ThreadFindProvider {...findOptions} controlsRef={controlsRef} onOpenChange={onOpenChange}>
<ThreadFindProvider {...findOptions}>
<ChatCanvas {...canvasProps} />
</ThreadFindProvider>
);
}

/** Owns search updates below the chat view so cycling does not rerender the composer and panels. */
function ThreadFindProvider({
controlsRef,
onOpenChange,
children,
...options
}: Parameters<typeof useThreadFind>[0] & {
controlsRef: Ref<ThreadFindControls>;
onOpenChange: (open: boolean) => void;
children: ReactNode;
}) {
}: Parameters<typeof useThreadFind>[0] & { children: ReactNode }) {
const find = useThreadFind(options);
useImperativeHandle(controlsRef, () => ({ open: find.open, close: find.close }), [
find.open,
find.close,
]);
useLayoutEffect(() => onOpenChange(find.isOpen), [find.isOpen, onOpenChange]);
return (
<ThreadFindBarContext value={find.barProps}>
<ThreadFindTimelineContext value={find.timelineProps}>{children}</ThreadFindTimelineContext>
Expand Down
22 changes: 0 additions & 22 deletions apps/web/src/components/chat/threadFindActionBus.test.ts

This file was deleted.

80 changes: 0 additions & 80 deletions apps/web/src/components/chat/threadFindActionBus.ts

This file was deleted.

Loading
Loading