Skip to content

Commit 04fb3ed

Browse files
committed
fix(tool): humanize ToolPicker group headings (CL-8483)
Slugs like slack and google-drive render as Slack / Google Drive. A package string that is already a display name is kept. Drop the uppercase shout. Escape and empty-copy split are unchanged.
1 parent fd8c9c5 commit 04fb3ed

2 files changed

Lines changed: 29 additions & 6 deletions

File tree

‎src/ui/tool-picker.test.tsx‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const ITEMS: ComponentProps<typeof ToolPicker>["items"] = [
1010
{ id: "slack-react", name: "Add reaction", package: "slack" },
1111
{ id: "linear-create", name: "Create issue", package: "linear", status: "pending" },
1212
{ id: "linear-close", name: "Close issue", package: "linear", status: "disabled" },
13+
{ id: "gmail-send", name: "Send email", package: "google-drive" },
1314
];
1415

1516
function mount(props: Partial<ComponentProps<typeof ToolPicker>> = {}) {
@@ -28,11 +29,23 @@ function mount(props: Partial<ComponentProps<typeof ToolPicker>> = {}) {
2829
}
2930

3031
describe("ToolPicker", () => {
31-
test("groups items by package", () => {
32+
test("groups items by package with humanized headings, not raw slugs", () => {
3233
const { container, unmount } = mount();
3334
const headings = [...container.querySelectorAll('p[role="presentation"], p')].map((el) => el.textContent);
34-
expect(headings).toContain("slack");
35-
expect(headings).toContain("linear");
35+
expect(headings).toContain("Slack");
36+
expect(headings).toContain("Linear");
37+
expect(headings).toContain("Google Drive");
38+
expect(headings).not.toContain("slack");
39+
expect(headings).not.toContain("google-drive");
40+
unmount();
41+
});
42+
43+
test("keeps a package string that is already a display name", () => {
44+
const { container, unmount } = mount({
45+
items: [{ id: "x", name: "Post message", package: "Slack" }],
46+
});
47+
const headings = [...container.querySelectorAll("p")].map((el) => el.textContent);
48+
expect(headings).toContain("Slack");
3649
unmount();
3750
});
3851

@@ -149,7 +162,7 @@ describe("ToolPicker", () => {
149162
act(() => {
150163
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }));
151164
});
152-
expect(options().length).toBe(4);
165+
expect(options().length).toBe(5);
153166

154167
const second = new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true });
155168
act(() => {

‎src/ui/tool-picker.tsx‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ function groupByPackage(items: readonly ToolPickerItem[]): { readonly id: string
6161
return [...groups.entries()].map(([id, groupItems]) => ({ id, items: groupItems }));
6262
}
6363

64+
/** Visible group title: a display name if the catalog already sent one, else a humanized slug. */
65+
function groupHeading(id: string): string {
66+
if (/[A-Z]/.test(id) || /\s/.test(id)) return id;
67+
return id
68+
.split(/[-_./]+/)
69+
.filter(Boolean)
70+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
71+
.join(" ");
72+
}
73+
6474
/**
6575
* Browse, search and select tools from a caller-supplied catalog, grouped by
6676
* package. Controlled like a form field (`value`/`onChange`) rather than
@@ -157,8 +167,8 @@ export function ToolPicker({
157167
) : (
158168
groups.map((group) => (
159169
<div key={group.id} role="presentation" className="flex flex-col gap-1">
160-
<p className="px-2 text-[11px] font-semibold tracking-[0.1em] text-muted-foreground uppercase">
161-
{group.id}
170+
<p className="px-2 text-[11px] font-semibold text-muted-foreground">
171+
{groupHeading(group.id)}
162172
</p>
163173
<div className="flex flex-col gap-1">
164174
{group.items.map((item) => (

0 commit comments

Comments
 (0)