diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx
index b5a5cff19..36565a31e 100644
--- a/frontend/src/components/workspace/messages/message-list.tsx
+++ b/frontend/src/components/workspace/messages/message-list.tsx
@@ -212,6 +212,7 @@ export function MessageList({
canRegenerate = false,
canBranch = false,
enableSidecarActions = true,
+ sidecarSurface = false,
initialScroll = "smooth",
resizeScroll = "smooth",
}: {
@@ -235,6 +236,7 @@ export function MessageList({
canRegenerate?: boolean;
canBranch?: boolean;
enableSidecarActions?: boolean;
+ sidecarSurface?: boolean;
initialScroll?: ConversationProps["initial"];
resizeScroll?: ConversationProps["resize"];
}) {
@@ -417,10 +419,18 @@ export function MessageList({
if (!selectionToolbar) {
return;
}
- sidecar?.addContextToConversation(selectionToolbar.context);
+ // On the sidecar surface, "add to conversation" targets the side chat's
+ // own composer (activeReferences) rather than the main composer's quotes,
+ // so the selected snippet is attached to the conversation the user is
+ // actually reading.
+ if (sidecarSurface) {
+ sidecar?.openContext(selectionToolbar.context);
+ } else {
+ sidecar?.addContextToConversation(selectionToolbar.context);
+ }
window.getSelection()?.removeAllRanges();
setSelectionToolbar(null);
- }, [selectionToolbar, sidecar]);
+ }, [selectionToolbar, sidecar, sidecarSurface]);
const handleAskSelectionInSidecar = useCallback(() => {
if (!selectionToolbar) {
@@ -873,17 +883,19 @@ export function MessageList({
{t.sidecar.addToConversation}
-
+ {!sidecarSurface && (
+
+ )}
diff --git a/frontend/tests/e2e/sidecar-chat.spec.ts b/frontend/tests/e2e/sidecar-chat.spec.ts
index b40264608..cc8e7823a 100644
--- a/frontend/tests/e2e/sidecar-chat.spec.ts
+++ b/frontend/tests/e2e/sidecar-chat.spec.ts
@@ -105,6 +105,32 @@ async function selectTextAndClickToolbarButton(
throw lastError;
}
+async function expectSidecarSelectionToolbarActions(page: Page, text: string) {
+ let lastError: unknown;
+ for (let attempt = 0; attempt < 3; attempt += 1) {
+ try {
+ await selectTextOnPage(page, text, "sidecar-message-list");
+ const labels = await page.evaluate(() =>
+ Array.from(
+ document.querySelectorAll(
+ "[data-sidecar-selection-toolbar] button",
+ ),
+ ).map((button) => button.textContent?.trim() ?? ""),
+ );
+ expect(labels.some((label) => /add to conversation/i.test(label))).toBe(
+ true,
+ );
+ expect(labels.some((label) => /ask in side chat/i.test(label))).toBe(
+ false,
+ );
+ return;
+ } catch (error) {
+ lastError = error;
+ }
+ }
+ throw lastError;
+}
+
async function expectComposerHeightsEqual(page: Page) {
const metrics = await page.evaluate(() => {
const findFormByPlaceholder = (pattern: RegExp) => {
@@ -943,30 +969,16 @@ test.describe("Side chat", () => {
.first(),
).toBeVisible();
+ // Selecting text inside the side chat itself only offers "Add to
+ // conversation" (no "Ask in side chat"), and the snippet attaches to the
+ // side chat's own composer rather than the main composer's quotes.
+ await expectSidecarSelectionToolbarActions(page, "Hello from DeerFlow!");
await selectTextAndClickToolbarButton(
page,
"Hello from DeerFlow!",
"Add to conversation",
"sidecar-message-list",
);
- await expect(
- mainInputForm.getByTestId("conversation-quote-attachment"),
- ).toContainText("1 selected text fragment");
- await expect(sidecarReference).toBeHidden();
- await mainInputForm
- .getByTestId("conversation-quote-attachment")
- .getByRole("button", { name: /clear selected references/i })
- .click();
- await expect(
- mainInputForm.getByTestId("conversation-quote-attachment"),
- ).toBeHidden();
-
- await selectTextAndClickToolbarButton(
- page,
- "Hello from DeerFlow!",
- "Ask in side chat",
- "sidecar-message-list",
- );
await expect(sidecarReference).toContainText("1 selected text fragment");
await expect(
mainInputForm.getByTestId("conversation-quote-attachment"),