From 9184a524fd96c8cc46739e3f75009ef91209d7b8 Mon Sep 17 00:00:00 2001 From: "Nick T." <73005126+tiliakoos@users.noreply.github.com> Date: Tue, 20 Oct 2026 12:23:15 -0400 Subject: [PATCH] fix(server): thread search shows quoted text instead of the citation link Search snippets came from the stored message text, where a quote is a t3-citation link. Build them from assistantCitationsToPlainText, as the queue, stash and thread-title previews already do. --- .../src/orchestration-v2/ThreadSearch.test.ts | 37 +++++++++++++++++++ .../src/orchestration-v2/ThreadSearch.ts | 3 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/ThreadSearch.test.ts b/apps/server/src/orchestration-v2/ThreadSearch.test.ts index 7ea506c7cab7b..1987d406b0e2c 100644 --- a/apps/server/src/orchestration-v2/ThreadSearch.test.ts +++ b/apps/server/src/orchestration-v2/ThreadSearch.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; import { + EnvironmentId, EventId, MessageId, ProjectId, @@ -16,6 +17,7 @@ import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as SqlClient from "effect/sql/SqlClient"; +import { serializeAssistantCitation } from "@t3tools/shared/assistantCitations"; import * as SqlitePersistence from "../persistence/Sqlite.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; @@ -179,6 +181,41 @@ it.layer(layerTest)("ThreadSearch", (it) => { }), ); + it.effect("shows a quoted assistant message as its text in the snippet", () => + Effect.gen(function* () { + const projections = yield* ProjectionStore.ProjectionStoreV2; + const search = yield* ThreadSearch.ThreadSearch; + const project = ProjectId.make("project:search-quote"); + const threadId = ThreadId.make("thread:quote"); + const quote = serializeAssistantCitation({ + version: 1, + environmentId: EnvironmentId.make("environment:local"), + threadId, + messageId: MessageId.make("quote-answer"), + text: "A lighthouse guides ships safely through the darkness.", + comment: "what are you talking about", + start: 0, + end: 54, + prefix: "", + suffix: "", + }); + yield* createProject(project); + yield* Effect.forEach( + [thread(threadId, project), message(threadId, "quote", "user", `Look here: ${quote}`)], + projections.apply, + { discard: true }, + ); + + const result = yield* search.search({ query: "talking" }); + assert.deepEqual( + result.matches.map((match) => match.snippet), + [ + "Look here: A lighthouse guides ships safely through the darkness. Comment: what are you talking about", + ], + ); + }), + ); + it.effect("reports an unreadable match as a decode failure", () => Effect.gen(function* () { const projections = yield* ProjectionStore.ProjectionStoreV2; diff --git a/apps/server/src/orchestration-v2/ThreadSearch.ts b/apps/server/src/orchestration-v2/ThreadSearch.ts index 0862c5160dad3..c624b637bde9e 100644 --- a/apps/server/src/orchestration-v2/ThreadSearch.ts +++ b/apps/server/src/orchestration-v2/ThreadSearch.ts @@ -12,6 +12,7 @@ import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import * as SqlClient from "effect/sql/SqlClient"; import * as SqlSchema from "effect/sql/SqlSchema"; +import { assistantCitationsToPlainText } from "@t3tools/shared/assistantCitations"; import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; export class ThreadSearchError extends Schema.TaggedError()( @@ -154,7 +155,7 @@ export const make = Effect.gen(function* () { threadId: row.threadId, projectId: row.projectId, source: row.source, - snippet: buildSearchSnippet(row.matchText, input.query), + snippet: buildSearchSnippet(assistantCitationsToPlainText(row.matchText), input.query), messageCreatedAt: row.messageCreatedAt, })), };