Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .changeset/chat-rail-disclosure-reads-as-dead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@agent-native/toolkit": patch
---

Give the sidebar chat rail's "more chats" control a disclosure chevron that
flips with its state instead of the `IconDots` glyph the chat rows above it
already use for their overflow menus. Hosts are free to pass the same label for
both disclosure states — Brain, Assets, Factory, Plan, and Dispatch all pass a
plain "Chats" — so the glyph was the only part of the control that could report
state, and it never moved. Pressing it did expand the rail, but the button
looked like a menu trigger that had silently failed.
57 changes: 57 additions & 0 deletions packages/toolkit/src/chat-history/ChatHistoryRail.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,63 @@ describe("ChatHistoryRail", () => {
expect(container.querySelectorAll(".an-chat-history-row")).toHaveLength(5);
});

it("changes the disclosure glyph with its state, even when both labels match", () => {
// Brain, Assets, Factory, and Plan all pass the same word for both
// disclosure states, so the accessible name and tooltip never move. The
// glyph has to carry the state change or the control reads as dead.
act(() => {
root.render(
<ChatHistoryRail
items={makeItems(8)}
onSelect={() => {}}
onNewChat={() => {}}
railLabels={{ ...railLabels, showMore: "Chats", showLess: "Chats" }}
/>,
);
});

const disclosure = container.querySelector<HTMLButtonElement>(
".an-chat-history-rail__disclosure",
);
const glyph = () =>
disclosure?.querySelector("svg")?.getAttribute("class") ?? "";

expect(glyph()).toContain("tabler-icon-chevron-down");
expect(glyph()).not.toContain("tabler-icon-dots");

act(() => disclosure?.click());
expect(glyph()).toContain("tabler-icon-chevron-up");
expect(disclosure?.getAttribute("aria-expanded")).toBe("true");

act(() => disclosure?.click());
expect(glyph()).toContain("tabler-icon-chevron-down");
expect(disclosure?.getAttribute("aria-expanded")).toBe("false");
});

it("does not reuse the row overflow glyph for the disclosure", () => {
act(() => {
root.render(
<ChatHistoryRail
items={makeItems(8)}
onSelect={() => {}}
onNewChat={() => {}}
onDelete={() => {}}
railLabels={railLabels}
/>,
);
});

const rowMenuGlyph = container
.querySelector(".an-chat-history-row__menu-trigger svg")
?.getAttribute("class");
const disclosureGlyph = container
.querySelector(".an-chat-history-rail__disclosure svg")
?.getAttribute("class");

expect(rowMenuGlyph).toContain("tabler-icon-dots");
expect(disclosureGlyph).not.toBe(rowMenuGlyph);
});

it("keeps the disclosure to the right of new chat and calls its handler", () => {
const onNewChat = vi.fn();
act(() => {
Expand Down
14 changes: 12 additions & 2 deletions packages/toolkit/src/chat-history/ChatHistoryRail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconDots, IconPlus } from "@tabler/icons-react";
import { IconChevronDown, IconChevronUp, IconPlus } from "@tabler/icons-react";
import type { ReactNode } from "react";

import { ActionButton, IconButton } from "../design-system/components.js";
Expand Down Expand Up @@ -80,7 +80,17 @@ export function DefaultChatHistoryRailView({
type="button"
className="an-chat-history-rail__disclosure"
size="compact"
icon={<IconDots size={14} strokeWidth={1.8} aria-hidden="true" />}
// A disclosure chevron, not the `IconDots` overflow glyph the rows
// above already use: hosts are free to give both states the same
// label, so the glyph is the only thing guaranteed to move when the
// rail expands.
icon={
expanded ? (
<IconChevronUp size={14} strokeWidth={1.8} aria-hidden="true" />
) : (
<IconChevronDown size={14} strokeWidth={1.8} aria-hidden="true" />
)
}
onPress={toggleExpanded}
aria-expanded={expanded}
label={disclosureLabel}
Expand Down
Loading