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
12 changes: 7 additions & 5 deletions src/client/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -597,10 +597,12 @@ export function App() {
<h1 className="truncate text-sm font-medium sm:text-base">
{workspaceMode === "chat" ? "Chat" : activeView.title}
</h1>
<Badge variant="secondary" className="hidden sm:inline-flex">
<Monitor data-icon="inline-start" />
{openAiReady ? "OpenAI ready" : "Local board"}
</Badge>
{workspaceMode === "board" && (
<Badge variant="secondary" className="hidden sm:inline-flex">
<Monitor data-icon="inline-start" />
{openAiReady ? "OpenAI ready" : "Local board"}
</Badge>
)}
</div>
<p className="hidden truncate text-sm text-muted-foreground xl:block">
{workspaceMode === "chat" || view === "settings"
Expand Down Expand Up @@ -697,7 +699,7 @@ export function App() {
onLoadHistory={loadAssistantChatHistory}
onAsk={askAssistant}
onMessagesChange={handleChatMessagesChange}
className="min-h-[calc(100dvh-8rem)] lg:static lg:h-[calc(100dvh-8rem)] xl:h-[calc(100dvh-8.5rem)]"
className="h-[calc(100dvh-5.5rem)] min-h-[32rem] lg:static lg:h-[calc(100dvh-8rem)] xl:h-[calc(100dvh-8.5rem)]"
/>
</Suspense>
</main>
Expand Down
64 changes: 64 additions & 0 deletions src/client/components/AssistantChatPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ describe("AssistantChatPanel", () => {

const textarea = container.querySelector<HTMLTextAreaElement>("#assistant-chat-input");
expect(textarea).not.toBeNull();
expect(
container.querySelector<HTMLElement>('[data-slot="card-title"]')?.textContent,
).toBe("Planning");
expect(container.textContent).not.toContain("ChatChat");

await act(async () => {
setTextareaValue(textarea!, "Plan my day");
Expand Down Expand Up @@ -143,6 +147,11 @@ describe("AssistantChatPanel", () => {
onLoadHistory: historyLoader(conversation, []),
});

const emptyState = container.querySelector<HTMLElement>('[data-slot="empty"]');
expect(emptyState?.textContent).toContain("Plan today");
expect(emptyState?.textContent).toContain("Break down drafts");
expect(emptyState?.textContent).toContain("Review stuck work");

await act(async () => {
getButtonByText("Plan today").dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
Expand All @@ -151,6 +160,57 @@ describe("AssistantChatPanel", () => {
expect(container.textContent).toContain("Response for");
});

it("opens board context from the compact context action", async () => {
const conversation = conversationFixture();

await renderPanel({
conversation,
onLoadHistory: historyLoader(conversation, []),
});

expect(document.body.textContent).not.toContain(
"Board counts available to the chat agent.",
);

await act(async () => {
container
.querySelector<HTMLButtonElement>('button[aria-label="Open context"]')
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});

expect(document.body.textContent).toContain("Board counts available to the chat agent.");
expect(document.body.querySelector('[data-slot="sheet-title"]')?.textContent).toBe(
"Current context",
);
});

it("shows a retry path when conversation history fails to load", async () => {
const conversation = conversationFixture();
const onLoadHistory = vi
.fn()
.mockRejectedValueOnce(new Error("History unavailable"))
.mockResolvedValueOnce({
conversations: [conversation],
activeConversationId: conversation.id,
messages: [],
});

await renderPanel({
conversation,
onLoadHistory,
});

expect(container.textContent).toContain("Could not load this conversation");
expect(container.textContent).toContain("History unavailable");

await act(async () => {
getButtonByText("Retry").dispatchEvent(new MouseEvent("click", { bubbles: true }));
});

expect(onLoadHistory).toHaveBeenCalledTimes(2);
expect(container.textContent).toContain("Start a chat");
});

it("renders proposed task actions and requires approval or dismissal", async () => {
const conversation = conversationFixture();
const proposedActions = [
Expand Down Expand Up @@ -194,6 +254,7 @@ describe("AssistantChatPanel", () => {
expect(container.textContent).toContain("Create launch task");
expect(container.textContent).toContain("Draft launch notes");
expect(container.textContent).toContain("The draft is ready");
expect(container.querySelector("dl")).toBeNull();

await act(async () => {
getButtonsByText("Approve")[0].dispatchEvent(new MouseEvent("click", { bubbles: true }));
Expand Down Expand Up @@ -222,6 +283,9 @@ describe("AssistantChatPanel", () => {

const textarea = container.querySelector<HTMLTextAreaElement>("#assistant-chat-input");
expect(textarea).not.toBeNull();
expect(textarea!.disabled).toBe(true);
expect(container.textContent).toContain("OpenAI setup required");
expect(getButtonByText("Open Settings")).not.toBeNull();

await act(async () => {
setTextareaValue(textarea!, "Plan my day");
Expand Down
Loading