feat(workstate): UI des résumés de conversation (Lot C frontend)
Affiche les résumés de conversation dans ProjectWorkStatePanel à partir du DTO camelCase : type de domaine et adaptateur mock alignés, panneau enrichi. Tests verts : workstate.test.tsx + projects.test.tsx (2 fichiers / 19), tsc --noEmit OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -189,9 +189,159 @@ describe("ProjectWorkStatePanel", () => {
|
||||
tickets: [],
|
||||
},
|
||||
],
|
||||
conversations: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("renders conversation objective and summary joined by conversation id", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-8",
|
||||
name: "Summarizer",
|
||||
profileId: "codex",
|
||||
busy: { state: "busy", ticket: "ticket-goal-abcdef", sinceMs: 123 },
|
||||
tickets: [
|
||||
{
|
||||
ticketId: "ticket-goal-abcdef",
|
||||
conversationId: "conversation-goal",
|
||||
position: 0,
|
||||
status: "inProgress",
|
||||
source: { kind: "human" },
|
||||
requesterLabel: "Anthony",
|
||||
taskPreview: "Continue implementation",
|
||||
taskLen: 23,
|
||||
},
|
||||
{
|
||||
ticketId: "ticket-summary-abcdef",
|
||||
conversationId: "conversation-summary",
|
||||
position: 1,
|
||||
status: "queued",
|
||||
source: { kind: "agent", agentId: "agent-main-123456" },
|
||||
requesterLabel: "Main",
|
||||
taskPreview: "Follow up",
|
||||
taskLen: 9,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
conversations: [
|
||||
{
|
||||
conversationId: "conversation-summary",
|
||||
status: "ready",
|
||||
objectivePreview: null,
|
||||
summaryPreview: "Previous turn established the adapter boundary.",
|
||||
summaryLen: 47,
|
||||
upTo: "turn-2",
|
||||
recentTurns: [],
|
||||
},
|
||||
{
|
||||
conversationId: "conversation-goal",
|
||||
status: "ready",
|
||||
objectivePreview: "Ship the Work panel summaries",
|
||||
summaryPreview: "This should be lower priority than the goal.",
|
||||
summaryLen: 48,
|
||||
upTo: "turn-1",
|
||||
recentTurns: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
renderPanel(workState);
|
||||
|
||||
const list = await screen.findByLabelText("Summarizer tickets");
|
||||
expect(within(list).getAllByText("Summary")).toHaveLength(2);
|
||||
expect(
|
||||
within(list).getByText("Goal: Ship the Work panel summaries"),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText(
|
||||
"Previous turn established the adapter boundary.",
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps partial and unavailable summaries compact without hiding tickets", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-9",
|
||||
name: "Contextualizer",
|
||||
profileId: "claude",
|
||||
busy: { state: "idle" },
|
||||
tickets: [
|
||||
{
|
||||
ticketId: "ticket-partial-abcdef",
|
||||
conversationId: "conversation-partial",
|
||||
position: 0,
|
||||
status: "inProgress",
|
||||
source: { kind: "agent", agentId: "agent-qa-123456" },
|
||||
requesterLabel: "QA",
|
||||
taskPreview: "Validate partial context",
|
||||
taskLen: 24,
|
||||
},
|
||||
{
|
||||
ticketId: "ticket-unavailable-abcdef",
|
||||
conversationId: "conversation-unavailable",
|
||||
position: 1,
|
||||
status: "queued",
|
||||
source: { kind: "human" },
|
||||
requesterLabel: "Anthony",
|
||||
taskPreview: "Validate unavailable context",
|
||||
taskLen: 28,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
conversations: [
|
||||
{
|
||||
conversationId: "conversation-partial",
|
||||
status: "partial",
|
||||
objectivePreview: null,
|
||||
summaryPreview: null,
|
||||
summaryLen: 0,
|
||||
upTo: null,
|
||||
recentTurns: [
|
||||
{
|
||||
role: "prompt",
|
||||
source: { kind: "agent", agentId: "agent-qa-123456" },
|
||||
atMs: 100,
|
||||
textPreview: "Only the latest prompt was available.",
|
||||
textLen: 37,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
conversationId: "conversation-unavailable",
|
||||
status: "unavailable",
|
||||
objectivePreview: null,
|
||||
summaryPreview: "Summary service unavailable right now.",
|
||||
summaryLen: 38,
|
||||
upTo: null,
|
||||
recentTurns: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
renderPanel(workState);
|
||||
|
||||
const list = await screen.findByLabelText("Contextualizer tickets");
|
||||
expect(within(list).getByText("#1 In progress")).toBeTruthy();
|
||||
expect(within(list).getByText("Validate partial context")).toBeTruthy();
|
||||
expect(within(list).getByText("Partial")).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText("Last: Only the latest prompt was available."),
|
||||
).toBeTruthy();
|
||||
expect(within(list).getByText("#2 Queued")).toBeTruthy();
|
||||
expect(within(list).getByText("Validate unavailable context")).toBeTruthy();
|
||||
expect(within(list).getByText("Unavailable")).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText("Summary service unavailable right now."),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("refreshes when a relevant domain event fires", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
const system = new MockSystemGateway();
|
||||
|
||||
Reference in New Issue
Block a user