Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ed97a41
feat(web): find messages and plans in the current thread
Yash-Singh1 Oct 6, 2026
f7b1e47
fix: preserve thread search navigation and consistent snapshots
Noojuno Oct 7, 2026
6c190f2
fix: address thread search review findings
Noojuno Oct 7, 2026
2aeae63
refactor(web): require server support for thread search
Noojuno Oct 7, 2026
00f3a5f
fix(web): preserve markdown transforms after rebase
Yash-Singh1 Oct 7, 2026
2c830e8
fix(web): keep thread search messages stable between matches
Yash-Singh1 Oct 7, 2026
212f151
fix(shared): preserve thread links in the markdown pipeline
Yash-Singh1 Oct 7, 2026
c032e82
perf(search): reuse thread match counts during navigation
Yash-Singh1 Oct 7, 2026
1e5accb
fix(search): wait for the timeline before positioning results
Yash-Singh1 Oct 7, 2026
0ec7e3e
fix(search): navigate the conversation from the current reading position
Yash-Singh1 Oct 7, 2026
8bd156c
Merge remote-tracking branch 'origin/main' into pr10439
juliusmarminge Oct 7, 2026
4414e91
fix(shared): keep isRelativeFilePath module-private
juliusmarminge Oct 7, 2026
0fdcad2
fix(search): count what code block headers and citation chips display
juliusmarminge Oct 7, 2026
73a7859
fix(web): keep find results expanded while typing and leave Mod+F to …
juliusmarminge Oct 7, 2026
7906e46
fix(server): thread search no longer holds the write lock or reparses…
juliusmarminge Oct 7, 2026
f775994
Merge remote-tracking branch 'origin/main' into pr10439
juliusmarminge Oct 7, 2026
e5c01b2
fix(web): find keeps its reading position and the bodies it opened
juliusmarminge Oct 7, 2026
e96f67d
fix(web): find opens folded content only for the selected match
juliusmarminge Oct 7, 2026
f6a5569
fix(web): find opens folded imported turns
juliusmarminge Oct 7, 2026
1843c75
fix(web): find opens disclosures that were closed before find started
juliusmarminge Oct 7, 2026
b463417
fix(web): find bar lines up with the workspace card
juliusmarminge Oct 7, 2026
6776c35
Merge remote-tracking branch 'origin/main' into pr10439
juliusmarminge Oct 7, 2026
fde0b42
fix(web): find starts inside a folded imported turn at the top of the…
juliusmarminge Oct 7, 2026
47b18f9
build: pin the shared Markdown pipeline in the pnpm catalog
juliusmarminge Oct 7, 2026
0e5bfbc
Merge remote-tracking branch 'origin/main' into pr10439
juliusmarminge Oct 7, 2026
0a0ae20
fix(search): address adversarial review findings
juliusmarminge Oct 7, 2026
0680c40
perf(web): speed up match navigation and track live results
Yash-Singh1 Oct 7, 2026
784260a
perf(search): progressively reveal thread matches
Yash-Singh1 Oct 8, 2026
50b2979
fix(web): retarget first search jump as history loads
Yash-Singh1 Oct 8, 2026
9b0f3bb
chore: merge origin/main into thread search
Yash-Singh1 Oct 8, 2026
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
6 changes: 3 additions & 3 deletions apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { fileBasename } from "@t3tools/shared/path";
import { formatFilePathPosition } from "@t3tools/shared/fileLinks";
import {
fileBasename,
formatFilePathPosition,
inlineCodeFilePathCandidate,
normalizeMarkdownLinkDestination,
parseMarkdownFileLink,
} from "@t3tools/client-runtime/markdown-links";
} from "@t3tools/shared/markdownLinks";
import { parseThreadLinkHref } from "@t3tools/shared/threadLinks";
import { videoMimeType } from "@t3tools/shared/video";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MarkdownNode } from "react-native-nitro-markdown/headless";
import { isMarkdownFileLinkLabel } from "@t3tools/client-runtime/markdown-links";
import { isMarkdownFileLinkLabel } from "@t3tools/shared/markdownLinks";
import { collectComposerInlineTokens } from "@t3tools/shared/composerInlineTokens";
import { imageMimeType } from "@t3tools/shared/image";
import { isWindowsAbsolutePath } from "@t3tools/shared/path";
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/features/threads/ThreadDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type {
import {
appendCodexArtifactTemplateUsePrompt,
type CodexArtifactTemplate,
} from "@t3tools/client-runtime/codex-artifact-templates";
} from "@t3tools/shared/codexArtifactTemplates";
import type { ThreadUserInputQuestion } from "@t3tools/client-runtime/state/thread-requests";
import {
presentPendingBackgroundWork,
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/features/threads/ThreadFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ import { writeComposerContextClipboard } from "../../lib/composerContextClipboar
import {
codexArtifactTemplatePresentationLabel,
type CodexArtifactTemplate,
} from "@t3tools/client-runtime/codex-artifact-templates";
} from "@t3tools/shared/codexArtifactTemplates";
import { resolveAssetUrl } from "@t3tools/client-runtime/state/assets";
import { isMarkdownFileLinkLabel } from "@t3tools/client-runtime/markdown-links";
import { isMarkdownFileLinkLabel } from "@t3tools/shared/markdownLinks";
import { getTextContent, type MarkdownNode } from "react-native-nitro-markdown/headless";
import { formatAttachmentSize } from "@t3tools/client-runtime/state/attachments";
import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
Expand All @@ -50,7 +50,7 @@ import { resolveViewedImageAsset } from "@t3tools/client-runtime/work-log/presen
import {
renderCodexFileCitationsAsMarkdown,
splitCodexArtifactTemplateMarkdown,
} from "@t3tools/client-runtime/codex-markdown-directives";
} from "@t3tools/shared/codexMarkdownDirectives";
import { CHAT_LIST_ANCHOR_OFFSET, resolveChatListAnchoredEndSpace } from "@t3tools/shared/chatList";
import { imageMimeType } from "@t3tools/shared/image";
import { videoMimeType } from "@t3tools/shared/video";
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/features/threads/fileChipMenu.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fileBasename } from "@t3tools/client-runtime/markdown-links";
import { fileBasename } from "@t3tools/shared/path";
import type { ThreadId } from "@t3tools/contracts";
import { resolveMarkdownLinkPresentation } from "@t3tools/mobile-markdown-text/links";
import type { MarkdownFileContextMenu } from "@t3tools/mobile-markdown-text/types";
Expand Down
43 changes: 2 additions & 41 deletions apps/mobile/src/features/threads/thread-search-match.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,9 @@
import { splitThreadSearchText } from "@t3tools/shared/threadSearch";
import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state/thread-search";

import { AppText as Text } from "../../components/AppText";
import { cn } from "../../lib/cn";

function foldAsciiCase(value: string): string {
return value.replace(/[A-Z]/g, (character) => character.toLowerCase());
}

function splitHighlightParts(text: string, query: string) {
const normalizedText = foldAsciiCase(text);
const normalizedQuery = foldAsciiCase(query.trim());
if (normalizedQuery.length === 0) {
return [{ text, highlighted: false, start: 0 }];
}

const parts: Array<{
readonly text: string;
readonly highlighted: boolean;
readonly start: number;
}> = [];
let cursor = 0;
while (cursor < text.length) {
const matchIndex = normalizedText.indexOf(normalizedQuery, cursor);
if (matchIndex === -1) {
parts.push({ text: text.slice(cursor), highlighted: false, start: cursor });
break;
}
if (matchIndex > cursor) {
parts.push({
text: text.slice(cursor, matchIndex),
highlighted: false,
start: cursor,
});
}
parts.push({
text: text.slice(matchIndex, matchIndex + normalizedQuery.length),
highlighted: true,
start: matchIndex,
});
cursor = matchIndex + normalizedQuery.length;
}
return parts;
}

export function ThreadSearchMatchExcerpt(props: {
readonly match: EnvironmentThreadSearchMatch;
readonly query: string;
Expand All @@ -51,7 +12,7 @@ export function ThreadSearchMatchExcerpt(props: {
readonly sidebar?: boolean;
}) {
const isUser = props.match.source === "user";
const parts = splitHighlightParts(props.match.snippet, props.query);
const parts = splitThreadSearchText(props.match.snippet, props.query);
return (
<Text
className={cn(
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/auth/RpcAuthorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const RPC_REQUIRED_SCOPES = {
[ORCHESTRATION_V2_WS_METHODS.getTurnDiff]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.getFullThreadDiff]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.searchThreads]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.searchThread]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.searchThreadStream]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.getArchivedShellSnapshot]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.getThreadProjection]: AuthOrchestrationReadScope,
[ORCHESTRATION_V2_WS_METHODS.getTurnItem]: AuthOrchestrationReadScope,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/observability/RpcInstrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const RPC_AGGREGATES = {
[ORCHESTRATION_V2_WS_METHODS.getTurnDiff]: "orchestration",
[ORCHESTRATION_V2_WS_METHODS.getFullThreadDiff]: "orchestration",
[ORCHESTRATION_V2_WS_METHODS.searchThreads]: "orchestration",
[ORCHESTRATION_V2_WS_METHODS.searchThread]: "orchestration",
[ORCHESTRATION_V2_WS_METHODS.searchThreadStream]: "orchestration",
[ORCHESTRATION_V2_WS_METHODS.getArchivedShellSnapshot]: "orchestration",
[ORCHESTRATION_V2_WS_METHODS.getThreadProjection]: "orchestrationV2",
[ORCHESTRATION_V2_WS_METHODS.launchThread]: "orchestration",
Expand Down
42 changes: 42 additions & 0 deletions apps/server/src/orchestration-v2/Orchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import type {
OrchestrationV2SearchThreadInput,
OrchestrationV2SearchThreadResult,
OrchestrationV2ThreadHistoryPage,
} from "@t3tools/contracts";
import {
latestExecutedRun,
latestRootProviderFailure,
Expand Down Expand Up @@ -280,6 +285,18 @@ export interface OrchestratorV2Shape {
readonly dispatch: (
command: OrchestrationV2ServerCommand,
) => Effect.Effect<OrchestratorV2DispatchResult, OrchestratorV2Error>;
readonly searchThreadStream: (
input: OrchestrationV2SearchThreadInput,
) => Stream.Stream<OrchestrationV2SearchThreadResult, OrchestratorV2Error>;
readonly searchThread: (
input: OrchestrationV2SearchThreadInput,
) => Effect.Effect<OrchestrationV2SearchThreadResult, OrchestratorV2Error>;
readonly getThreadHistoryPage: (
threadId: ThreadId,
cursor: string,
throughEntryId?: string,
conversationOnly?: boolean,
) => Effect.Effect<OrchestrationV2ThreadHistoryPage, OrchestratorV2Error>;
readonly getTimelinePage: (
threadId: ThreadId,
options: ProjectionTimelinePageOptions,
Expand Down Expand Up @@ -10808,6 +10825,26 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
recoverDelegatedTask,
delegatedTaskResultPending,
dispatch: dispatchWithReceipt,
searchThreadStream: (input) =>
projectionStore
.searchThreadStream(input)
.pipe(
Stream.mapError(
(cause) => new OrchestratorProjectionError({ threadId: input.threadId, cause }),
),
),
searchThread: (input) =>
projectionStore
.searchThread(input)
.pipe(
Effect.mapError(
(cause) => new OrchestratorProjectionError({ threadId: input.threadId, cause }),
),
),
getThreadHistoryPage: (threadId, cursor, throughEntryId, conversationOnly) =>
projectionStore
.getThreadHistoryPage(threadId, cursor, throughEntryId, conversationOnly)
.pipe(Effect.mapError((cause) => new OrchestratorProjectionError({ threadId, cause }))),
getTimelinePage: (threadId, options) =>
projectionStore
.getTimelinePage(threadId, options)
Expand Down Expand Up @@ -10943,6 +10980,11 @@ const layerUnavailable: Layer.Layer<OrchestratorV2> = Layer.succeed(
cause: "Orchestration V2 live runtime is not configured.",
}),
),
searchThreadStream: (input) =>
Stream.fail(new OrchestratorProjectionError({ threadId: input.threadId })),
searchThread: (input) =>
Effect.fail(new OrchestratorProjectionError({ threadId: input.threadId })),
getThreadHistoryPage: (threadId) => Effect.fail(new OrchestratorProjectionError({ threadId })),
getTimelinePage: (threadId) => Effect.fail(new OrchestratorProjectionError({ threadId })),
getMessageCount: (threadId) => Effect.fail(new OrchestratorProjectionError({ threadId })),
getTurnItem: ({ threadId }) => Effect.fail(new OrchestratorProjectionError({ threadId })),
Expand Down
Loading
Loading