P1 exit: chat UI, sessions column, agent create — 11 E2E journeys green
- Stream layer: incremental SSE parser tested against bytes captured from
the REAL gateway (chunking-independent, UTF-8-safe, heartbeat-tolerant);
Zod gateway event schemas; pure transcript reducer (optimistic send,
delta streaming, step traces, resume dedupe, error states)
- Same-origin /api proxy route: httpOnly cookie -> bearer, unbuffered SSE
passthrough; NuqsAdapter in root layout
- Chat workspace: route /claws/{id}/chat/{key} (RSC history + settings),
WelcomeState with suggested prompts, MessageList (right user bubbles,
left agent messages, blink caret), collapsible StepTrace, Composer
(Enter sends, Shift+Enter newline)
- SessionsColumn in 208px SlidePanel (?sessions=1): search, relative
times, active coral border, new session; /claws/{id} resumes latest or
opens fresh; minimal create-claw form; rail + button and roster links
- P1 exit E2E: scripted reply streams, tool step trace survives reload,
separate transcripts across sessions with column switching, create claw
and chat immediately
83 Rust + 56 frontend unit/component tests + 11 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
32008c9ef0
commit
9f9f507c15
@@ -0,0 +1,47 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { Composer } from "./Composer";
|
||||
|
||||
describe("Composer", () => {
|
||||
it("sends trimmed text on Enter and clears", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSend = vi.fn();
|
||||
render(<Composer agentName="Scout" disabled={false} onSend={onSend} />);
|
||||
const box = screen.getByLabelText("Message Scout");
|
||||
|
||||
await user.type(box, " hello there {Enter}");
|
||||
expect(onSend).toHaveBeenCalledWith("hello there");
|
||||
expect(box).toHaveValue("");
|
||||
});
|
||||
|
||||
it("Shift+Enter inserts a newline instead of sending", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSend = vi.fn();
|
||||
render(<Composer agentName="Scout" disabled={false} onSend={onSend} />);
|
||||
const box = screen.getByLabelText("Message Scout");
|
||||
|
||||
await user.type(box, "line one{Shift>}{Enter}{/Shift}line two");
|
||||
expect(onSend).not.toHaveBeenCalled();
|
||||
expect(box).toHaveValue("line one\nline two");
|
||||
});
|
||||
|
||||
it("does not send empty input", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSend = vi.fn();
|
||||
render(<Composer agentName="Scout" disabled={false} onSend={onSend} />);
|
||||
await user.type(screen.getByLabelText("Message Scout"), " {Enter}");
|
||||
expect(onSend).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks sending while streaming", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSend = vi.fn();
|
||||
render(<Composer agentName="Scout" disabled onSend={onSend} />);
|
||||
const box = screen.getByLabelText("Message Scout");
|
||||
await user.type(box, "hi{Enter}");
|
||||
expect(onSend).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user