Skip to content

Commit 0d865b7

Browse files
committed
refactor(chat): show browser chip as icon-only and search by query
- Render Browser MCP chip as compact icon with aria-label instead of text - Prefer search query/pattern over scope path in command and tool summaries
1 parent 0a1f9e6 commit 0d865b7

8 files changed

Lines changed: 39 additions & 59 deletions

File tree

src/renderer/components/composer/AttachmentBar.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ import type { Attachment } from "./useAttachments";
77
export function BrowserChip(props: { onRemove?: (() => void) | undefined; title?: string }) {
88
const { onRemove, title = "Browser MCP enabled for this thread" } = props;
99
return (
10-
<div className="lightcode-attachment-chip lightcode-browser-chip" title={title}>
11-
<Globe className="size-3 text-muted" />
12-
<span className="lightcode-attachment-chip__name">Browser</span>
10+
<div
11+
className="lightcode-attachment-chip lightcode-browser-chip"
12+
title={title}
13+
aria-label={title}
14+
role={onRemove ? "group" : "img"}
15+
>
16+
<Globe className="size-3 text-muted" aria-hidden="true" />
1317
{onRemove ? (
1418
<button
1519
type="button"

src/renderer/components/thread/ChatPane/parts/items/ToolCallGroup.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,7 @@ describe("ToolCallGroup", () => {
215215
);
216216

217217
expect(document.body).toHaveTextContent("View 1:24: src/supervisor/runtime.test.ts");
218-
expect(document.body).toHaveTextContent("Search:");
219-
expect(document.body).toHaveTextContent(/node_modules.*pnpm/);
218+
expect(screen.getByText('Search: "vitest.mjs"')).toBeInTheDocument();
220219
expect(screen.getByText("Git: git diff -- src/supervisor/runtime.ts")).toBeInTheDocument();
221220
expect(screen.getByText("Check: pnpm run test")).toBeInTheDocument();
222221
expect(screen.getByText("Install packages: pnpm install")).toBeInTheDocument();

src/renderer/components/thread/ChatPane/parts/items/commandSummary.test.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,25 +96,21 @@ describe("humanIntentTitle", () => {
9696

9797
it("describes ripgrep commands as searches", () => {
9898
const full = `/bin/zsh -lc 'rg -n "agent status|AgentStatus" src/main src/supervisor src/shared -S'`;
99-
expect(humanIntentTitle(full)).toBe("Search: src/main src/supervisor src/shared");
99+
expect(humanIntentTitle(full)).toBe('Search: "agent status|AgentStatus"');
100100
expect(commandIntentDisplay(full).kind).toBe("search");
101-
expect(commandIntentDisplay(full).parts).toEqual({
102-
prefix: "Search: ",
103-
path: "src/main src/supervisor src/shared",
104-
});
101+
expect(commandIntentDisplay(full).parts).toBeUndefined();
105102
});
106103

107104
it("describes plain grep commands as searches", () => {
108105
const full = `grep -n "toastId" src/renderer/notifications.ts`;
109-
expect(humanIntentTitle(full)).toBe("Search: src/renderer/notifications.ts");
106+
expect(humanIntentTitle(full)).toBe('Search: "toastId"');
110107
expect(commandIntentDisplay(full).kind).toBe("search");
111108
});
112109

113110
it("describes recursive grep with multiple paths as a search", () => {
114111
const full = `grep -rn "filteredCommands" src/renderer src/shared`;
115112
expect(commandIntentDisplay(full)).toEqual({
116-
title: "Search: src/renderer src/shared",
117-
parts: { prefix: "Search: ", path: "src/renderer src/shared" },
113+
title: 'Search: "filteredCommands"',
118114
kind: "search",
119115
});
120116
});
@@ -126,7 +122,7 @@ describe("humanIntentTitle", () => {
126122

127123
it("handles grep -e PATTERN form", () => {
128124
const full = `grep -rn -e "needle" src`;
129-
expect(humanIntentTitle(full)).toBe("Search: src");
125+
expect(humanIntentTitle(full)).toBe('Search: "needle"');
130126
});
131127

132128
it("describes cat piped through sed as viewed lines", () => {
@@ -151,12 +147,9 @@ describe("humanIntentTitle", () => {
151147

152148
it("describes find commands as searches", () => {
153149
const full = `find node_modules/.pnpm -maxdepth 4 -type f -name 'vitest.mjs' | sed -n '1,80p'`;
154-
expect(humanIntentTitle(full)).toBe("Search: node_modules/.pnpm");
150+
expect(humanIntentTitle(full)).toBe('Search: "vitest.mjs"');
155151
expect(commandIntentDisplay(full).kind).toBe("search");
156-
expect(commandIntentDisplay(full).parts).toEqual({
157-
prefix: "Search: ",
158-
path: "node_modules/.pnpm",
159-
});
152+
expect(commandIntentDisplay(full).parts).toBeUndefined();
160153
});
161154

162155
it("describes directory listings and package manager commands", () => {

src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,6 @@ function intentFromSummarizedCommand(t: string): CommandIntentDisplay | null {
181181

182182
const grepLike = parseGrepLikeSearch(trimmed);
183183
if (grepLike) {
184-
if (grepLike.scope) {
185-
const prefix = "Search: ";
186-
return {
187-
title: `${prefix}${grepLike.scope}`,
188-
parts: { prefix, path: grepLike.scope },
189-
kind: "search",
190-
};
191-
}
192184
return {
193185
title: `Search: "${grepLike.pattern}"`,
194186
kind: "search",
@@ -197,10 +189,14 @@ function intentFromSummarizedCommand(t: string): CommandIntentDisplay | null {
197189

198190
const findSearch = parseFindSearch(trimmed);
199191
if (findSearch) {
200-
const prefix = "Search: ";
192+
if (findSearch.pattern) {
193+
return {
194+
title: `Search: "${findSearch.pattern}"`,
195+
kind: "search",
196+
};
197+
}
201198
return {
202-
title: `${prefix}${findSearch.scope}`,
203-
parts: { prefix, path: findSearch.scope },
199+
title: `Search: ${findSearch.scope}`,
204200
kind: "search",
205201
};
206202
}

src/renderer/components/thread/ChatPane/parts/items/toolDisplay.test.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ describe("deriveToolDisplay", () => {
7878
expect(display.Icon).toBe(Pencil);
7979
});
8080

81-
it("labels ACP local search tools with only the scope", () => {
81+
it("labels ACP local search tools with only the query", () => {
8282
const display = deriveToolDisplay(
8383
makePayload({
8484
name: "'attachment' in src/renderer/**",
@@ -88,11 +88,8 @@ describe("deriveToolDisplay", () => {
8888
}),
8989
);
9090

91-
expect(display.title).toBe("Search: src/renderer/**");
92-
expect(display.parts).toEqual({
93-
prefix: "Search: ",
94-
path: "src/renderer/**",
95-
});
91+
expect(display.title).toBe('Search: "attachment"');
92+
expect(display.parts).toBeUndefined();
9693
expect(display.Icon).toBe(SearchCode);
9794
});
9895

@@ -106,11 +103,8 @@ describe("deriveToolDisplay", () => {
106103
}),
107104
);
108105

109-
expect(display.title).toBe("Search: src");
110-
expect(display.parts).toEqual({
111-
prefix: "Search: ",
112-
path: "src",
113-
});
106+
expect(display.title).toBe(String.raw`Search: "\"document\"|\"image\"|\"other\""`);
107+
expect(display.parts).toBeUndefined();
114108
expect(display.Icon).toBe(SearchCode);
115109
});
116110

src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -392,14 +392,6 @@ function formatAcpSearchDisplay(
392392
const pattern = readStr(args, "pattern");
393393
const scope = readScope(args) ?? locationPath;
394394
const searchTerm = query ?? pattern;
395-
if (searchTerm && scope) {
396-
const prefix = "Search: ";
397-
return {
398-
title: `${prefix}${scope}`,
399-
Icon: SearchCode,
400-
parts: { prefix, path: scope },
401-
};
402-
}
403395
if (searchTerm) return { title: `Search: "${searchTerm}"`, Icon: SearchCode };
404396
if (scope) return withTarget("Search", scope, SearchCode);
405397
return title.toLowerCase().startsWith("search")

src/renderer/components/thread/ThreadView.test.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe("ThreadView", () => {
7373
});
7474
});
7575

76-
it("renders Browser MCP as a removable header chip", () => {
76+
it("renders Browser MCP as a removable header icon", () => {
7777
const onConfigChange = vi.fn<(config: ThreadConfig) => void>();
7878

7979
renderThreadView({
@@ -123,11 +123,10 @@ describe("ThreadView", () => {
123123
onSubmitInput: async () => undefined,
124124
});
125125

126-
const browserLabels = screen.getAllByText("Browser");
127-
expect(browserLabels).toHaveLength(1);
128-
expect(
129-
hasAncestorWithClassFragment(browserLabels[0]!, "lightcode-overlay-header__controls"),
130-
).toBe(true);
126+
const browserIcon = screen.getByLabelText("Browser MCP enabled for this thread");
127+
expect(hasAncestorWithClassFragment(browserIcon, "lightcode-overlay-header__controls")).toBe(
128+
true,
129+
);
131130

132131
fireEvent.click(screen.getByLabelText("Disable Browser MCP"));
133132

@@ -137,7 +136,7 @@ describe("ThreadView", () => {
137136
});
138137
});
139138

140-
it("renders OpenCode Browser MCP as a read-only header chip when provider setting is enabled", () => {
139+
it("renders OpenCode Browser MCP as a read-only header icon when provider setting is enabled", () => {
141140
const onConfigChange = vi.fn<(config: ThreadConfig) => void>();
142141

143142
renderThreadView({
@@ -186,7 +185,7 @@ describe("ThreadView", () => {
186185
onSubmitInput: async () => undefined,
187186
});
188187

189-
expect(screen.getByText("Browser")).toBeInTheDocument();
188+
expect(screen.getByLabelText("Browser MCP enabled for OpenCode")).toBeInTheDocument();
190189
expect(screen.queryByLabelText("Disable Browser MCP")).toBeNull();
191190
expect(onConfigChange).not.toHaveBeenCalled();
192191
});

src/renderer/styles.css

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,8 +1891,11 @@ html[data-platform="darwin"] .lightcode-content-over-drag-region--drag {
18911891
}
18921892

18931893
.lightcode-browser-chip {
1894-
gap: 0.25rem;
1895-
padding: 0.12rem 0.38rem;
1894+
justify-content: center;
1895+
width: 1.35rem;
1896+
height: 1.35rem;
1897+
gap: 0;
1898+
padding: 0;
18961899
border-radius: 0.35rem;
18971900
font-size: 0.6875rem;
18981901
line-height: 1.25;

0 commit comments

Comments
 (0)