FN-7344: replace Activity subtabs with a dropdown
Replace the task detail Activity subtab strip with a compact selector while keeping legacy routing intact. - Swap the Live, Feed, and Raw Logs segmented control for an Activity view dropdown. - Preserve legacy Activity segment ids and initial-tab routing for Live, Feed, and Raw views. - Update Activity styling, coverage, and the published CLI changeset for the dropdown behavior. Files changed: .changeset/fn-7344-activity-dropdown.md | 7 ++ .../dashboard/app/components/TaskDetailModal.css | 63 +++--------------- .../dashboard/app/components/TaskDetailModal.tsx | 56 +++++++--------- .../TaskDetailModal.attachments-and-tabs.test.tsx | 76 ++++++++++++---------- .../__tests__/TaskDetailModal.css.test.ts | 10 +-- ...lModal.inline-editing-and-integrations.test.tsx | 2 +- ...skDetailModal.models-progress-workflow.test.tsx | 12 ++-- .../__tests__/TaskDetailModal.rendering.test.tsx | 2 +- ...etailModal.responsive-and-dependencies.test.tsx | 43 +++++------- .../TaskDetailModal.task-activity-chat.test.tsx | 39 ++++++----- .../components/__tests__/TaskDetailModal.test.tsx | 28 ++++---- 11 files changed, 153 insertions(+), 185 deletions(-) Fusion-Task-Id: FN-7344 Fusion-Task-Lineage: cb609122-d9b6-4ef2-9cf0-35130c3dcffc Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7344-activity-dropdown.md
Normal file
7
.changeset/fn-7344-activity-dropdown.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Replace task Activity subtabs with a compact Live, Feed, Raw dropdown.
|
||||
category: fix
|
||||
dev: Keeps legacy Activity segment ids and initial-tab routing while removing the old segmented-control shell.
|
||||
@@ -2434,13 +2434,10 @@ FNXC:TaskDetailTabs 2026-06-26-00:35:
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* === Activity Segmented Control === */
|
||||
/* === Activity View Selector === */
|
||||
/*
|
||||
FNXC:TaskDetailActivity 2026-06-30-23:55:
|
||||
The Activity top-level tab owns Live (internal `current`), Feed, and Raw Logs as an in-content segmented control. Keep this selector visually quieter than the top-level tab strip and horizontally reachable on narrow modal and embedded task-detail surfaces; the Activity-wide expand control must sit beside it on every segment.
|
||||
|
||||
FNXC:TaskDetailActivity 2026-06-30-12:00:
|
||||
Selected Activity segments must stay the same height as inactive siblings while using a slightly smaller label. Keep sizing on the base segment contract so visual active-state styling cannot alter the Live/Feed/Raw Logs row height.
|
||||
FNXC:TaskDetailActivity 2026-06-30-15:50:
|
||||
The Activity top-level tab owns Live (internal `current`), Feed, and Raw as one dropdown rather than in-content subtabs. Keep the selector compact and token-styled on desktop/mobile, with the Activity-wide expand control beside it on every view.
|
||||
*/
|
||||
.activity-toolbar {
|
||||
display: flex;
|
||||
@@ -2450,54 +2447,13 @@ Selected Activity segments must stay the same height as inactive siblings while
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.activity-segmented-control {
|
||||
display: inline-flex;
|
||||
max-inline-size: 100%;
|
||||
gap: 0;
|
||||
background: var(--card);
|
||||
border: var(--border-width) solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: calc(var(--space-xs) / 4);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-inline: contain;
|
||||
touch-action: pan-x pan-y;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.activity-segment {
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 auto;
|
||||
.activity-view-select {
|
||||
min-inline-size: calc(var(--space-2xl) + var(--space-xl) + var(--space-lg));
|
||||
min-block-size: var(--space-2xl);
|
||||
padding: calc(var(--space-xs) / 2) var(--space-md);
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
padding-block: calc(var(--space-xs) / 2);
|
||||
padding-inline: var(--space-sm) var(--space-xl);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-tight);
|
||||
font-family: inherit;
|
||||
touch-action: pan-x pan-y;
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
}
|
||||
|
||||
.activity-segment:hover {
|
||||
color: var(--text);
|
||||
background: var(--card-hover);
|
||||
}
|
||||
|
||||
.activity-segment:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring-strong);
|
||||
}
|
||||
|
||||
.activity-segment-active {
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.activity-expand-toggle {
|
||||
@@ -2524,8 +2480,9 @@ Selected Activity segments must stay the same height as inactive siblings while
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.activity-segmented-control {
|
||||
.activity-view-select {
|
||||
flex: 1 1 auto;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.activity-expand-toggle {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "./TaskDetailModal.css";
|
||||
import React, { Suspense, lazy, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import React, { Suspense, lazy, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Pencil, Bot, X, ChevronDown, ChevronRight, GitBranch, ArrowLeft, Zap, Loader2, AlertTriangle, Sparkles, Maximize2, Minimize2 } from "lucide-react";
|
||||
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
||||
@@ -213,6 +213,9 @@ The first Activity segment keeps the stable internal `current` id for legacy seg
|
||||
|
||||
FNXC:TaskDetailActivity 2026-06-30-23:55:
|
||||
The first Activity segment is user-facing Live while legacy internals remain `current` and explicit `initialTab="chat"` continues landing there for compatibility.
|
||||
|
||||
FNXC:TaskDetailActivity 2026-06-30-15:50:
|
||||
Activity view switching uses one dropdown labeled for Live, Feed, and Raw while retaining the internal `current`, `feed`, and `raw-logs` segment ids. Legacy `chat` and `logs` initial-tab routing remains compatible so older links still open Activity → Live or Activity → Feed.
|
||||
*/
|
||||
function resolveDefaultTab(initialTab: TabId | undefined, column: ColumnId, taskDetailChatFirst = false): TabId {
|
||||
if (initialTab === "retries") {
|
||||
@@ -548,6 +551,7 @@ export function TaskDetailContent({
|
||||
workflowFieldDefs: workflowFieldDefsProp,
|
||||
}: TaskDetailContentProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const activitySelectorId = useId();
|
||||
const columnLabel = useColumnLabel();
|
||||
const fileBrowser = useFileBrowser();
|
||||
const [activeTab, setActiveTab] = useState<TabId>(() => resolveDefaultTab(initialTab, task.column, taskDetailChatFirst));
|
||||
@@ -3412,41 +3416,29 @@ export function TaskDetailContent({
|
||||
<div className={`detail-section detail-section--activity${activitySegment === "current" || isActivityExpanded ? " detail-section--chat" : ""}${activitySegment === "raw-logs" ? " detail-section--agent-log" : ""}`}>
|
||||
{/*
|
||||
FNXC:TaskDetailPlannerChat 2026-06-30-22:30:
|
||||
Activity owns the existing steering/current view, Feed, and Raw Logs inside one segmented control. The stable Activity tab id remains `chat`, legacy `logs` callers land on Feed, and Raw Logs is the only segment that enables raw agent-log fetching. Planner-model conversation belongs to the separate `planner-chat` tab and must not route into steering comments.
|
||||
Activity owns the existing steering/current view, Feed, and raw agent logs inside one compact selector. The stable Activity tab id remains `chat`, legacy `logs` callers land on Feed, and Raw is the only selector option that enables raw agent-log fetching. Planner-model conversation belongs to the separate `planner-chat` tab and must not route into steering comments.
|
||||
|
||||
FNXC:TaskDetailActivity 2026-06-30-23:55:
|
||||
The first Activity segment is user-facing Live but keeps the legacy `current` segment id. Activity expansion is segment-wide, so the same reachable toggle must remain present on Live, Feed, and Raw Logs without fetching Raw Logs outside the Raw Logs segment.
|
||||
The first Activity segment is user-facing Live but keeps the legacy `current` segment id. Activity expansion is segment-wide, so the same reachable toggle must remain present on Live, Feed, and Raw without fetching Raw outside the Raw segment.
|
||||
|
||||
FNXC:TaskDetailActivity 2026-06-30-15:50:
|
||||
Replacing the in-content subtabs with a native dropdown removes the horizontal tab shell on mobile while preserving keyboard operation and the legacy Activity segment ids (`current`, `feed`, `raw-logs`).
|
||||
*/}
|
||||
<div className="activity-toolbar">
|
||||
<div className="activity-segmented-control" role="tablist" aria-label={t("taskDetail.activity.segmentsLabel", "Activity views")}>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activitySegment === "current"}
|
||||
className={`activity-segment${activitySegment === "current" ? " activity-segment-active" : ""}`}
|
||||
onClick={() => setActivitySegment("current")}
|
||||
>
|
||||
{t("taskDetail.activity.current", "Live")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activitySegment === "feed"}
|
||||
className={`activity-segment${activitySegment === "feed" ? " activity-segment-active" : ""}`}
|
||||
onClick={() => setActivitySegment("feed")}
|
||||
>
|
||||
{t("taskDetail.activity.feed", "Feed")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activitySegment === "raw-logs"}
|
||||
className={`activity-segment${activitySegment === "raw-logs" ? " activity-segment-active" : ""}`}
|
||||
onClick={() => setActivitySegment("raw-logs")}
|
||||
>
|
||||
{t("taskDetail.activity.rawLogs", "Raw Logs")}
|
||||
</button>
|
||||
</div>
|
||||
<label className="visually-hidden" htmlFor={activitySelectorId}>
|
||||
{t("taskDetail.activity.selectorLabel", "Activity view")}
|
||||
</label>
|
||||
<select
|
||||
id={activitySelectorId}
|
||||
className="select activity-view-select"
|
||||
value={activitySegment}
|
||||
onChange={(event) => setActivitySegment(event.target.value as ActivitySegment)}
|
||||
aria-label={t("taskDetail.activity.selectorLabel", "Activity view")}
|
||||
>
|
||||
<option value="current">{t("taskDetail.activity.current", "Live")}</option>
|
||||
<option value="feed">{t("taskDetail.activity.feed", "Feed")}</option>
|
||||
<option value="raw-logs">{t("taskDetail.activity.raw", "Raw")}</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm activity-expand-toggle"
|
||||
|
||||
@@ -30,10 +30,18 @@ FNXC:TaskDetailTabs 2026-06-17-08:20:
|
||||
FN-7306 labels the stable internal `chat` tab as Activity, while later Chat-first detail work keeps that legacy `chat` id only for explicit Activity requests. Definition-tab regression coverage must prove omitted non-done task details now land on planner Chat, Activity remains selectable, and explicit `initialTab="definition"` still opens the Definition surface for prompt, GitHub tracking, and dependency sections.
|
||||
|
||||
FNXC:TaskDetailPlannerChat 2026-06-30-23:58:
|
||||
Omitted non-done TaskDetailModal renders open the top-level planner Chat first/default. Activity controls (`Live`, `Feed`, `Raw Logs`, and the activity expand toggle) are intentionally mounted only after selecting Activity or using an explicit legacy Activity tab request.
|
||||
Omitted non-done TaskDetailModal renders open the top-level planner Chat first/default. Activity controls (`Live`, `Feed`, `Raw`, and the activity expand toggle) are intentionally mounted only after selecting Activity or using an explicit legacy Activity tab request.
|
||||
*/
|
||||
setupTaskDetailModalHooks();
|
||||
|
||||
function activitySelector(): HTMLSelectElement {
|
||||
return screen.getByRole("combobox", { name: "Activity view" }) as HTMLSelectElement;
|
||||
}
|
||||
|
||||
function selectActivityView(value: "current" | "feed" | "raw-logs") {
|
||||
fireEvent.change(activitySelector(), { target: { value } });
|
||||
}
|
||||
|
||||
describe("TaskDetailModal", () => {
|
||||
describe("paste image upload", () => {
|
||||
it("uploads an image when pasting clipboard image data", async () => {
|
||||
@@ -381,7 +389,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Select Activity before asserting its segmented controls
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
|
||||
const activityList = container.querySelector(".detail-activity-list");
|
||||
expect(activityList).toBeTruthy();
|
||||
@@ -472,21 +480,23 @@ describe("TaskDetailModal", () => {
|
||||
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAttribute("aria-label", "Collapse planner chat");
|
||||
expect(container.querySelector(".task-detail-content")).toHaveClass("task-detail-content--planner-chat-expanded");
|
||||
expect(container.querySelector(".activity-segmented-control")).toBeNull();
|
||||
expect(container.querySelector(".activity-segment")).toBeNull();
|
||||
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
|
||||
expect(screen.queryByText("Agent Log")).toBeNull();
|
||||
expect(screen.queryByRole("tab", { name: "Live" })).toBeNull();
|
||||
expect(screen.queryByRole("tab", { name: "Current" })).toBeNull();
|
||||
expect(screen.queryByRole("combobox", { name: "Activity view" })).toBeNull();
|
||||
expect(container.querySelector(".detail-section--chat")).toBeNull();
|
||||
expect(container.querySelector("[data-testid='task-chat-tab']")).toBeNull();
|
||||
expect(container.querySelector(".detail-activity")).toBeNull();
|
||||
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
expect(container.querySelector(".activity-segmented-control")).toBeTruthy();
|
||||
expect(screen.getByRole("tab", { name: "Live" })).toHaveAttribute("aria-selected", "true");
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
expect(container.querySelector(".activity-segmented-control")).toBeNull();
|
||||
expect(container.querySelector(".activity-segment")).toBeNull();
|
||||
expect(Array.from(activitySelector().options).map((option) => option.textContent?.trim())).toEqual(["Live", "Feed", "Raw"]);
|
||||
expect(activitySelector().value).toBe("current");
|
||||
selectActivityView("feed");
|
||||
expect(container.querySelector(".detail-activity")).toBeTruthy();
|
||||
expect(screen.getByRole("tab", { name: "Feed" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("feed");
|
||||
});
|
||||
|
||||
it("switches to Feed segment via Activity tab and shows activity feed", () => {
|
||||
@@ -509,7 +519,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Select Activity before asserting its segmented controls
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
|
||||
// Activity section should be visible
|
||||
expect(container.querySelector(".detail-activity")).toBeTruthy();
|
||||
@@ -540,7 +550,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Select Activity before asserting its segmented controls
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
|
||||
const activityList = container.querySelector(".detail-activity-list");
|
||||
expect(activityList).toBeTruthy();
|
||||
@@ -577,7 +587,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
|
||||
const actions = Array.from(container.querySelectorAll(".detail-log-action")).map((entry) => entry.textContent);
|
||||
const outcomes = Array.from(container.querySelectorAll(".detail-log-outcome")).map((entry) => entry.textContent);
|
||||
@@ -604,7 +614,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
|
||||
const actions = container.querySelectorAll(".detail-log-action");
|
||||
const outcomes = container.querySelectorAll(".detail-log-outcome");
|
||||
@@ -644,7 +654,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Select Activity before asserting its segmented controls
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
|
||||
// Activity section should be visible
|
||||
expect(container.querySelector(".detail-activity")).toBeTruthy();
|
||||
@@ -678,17 +688,17 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Select Activity, then Feed segment
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
expect(container.querySelector(".detail-activity")).toBeTruthy();
|
||||
expect(container.querySelector(".markdown-body")).toBeNull();
|
||||
|
||||
// Switch to Raw Activity segment within Activity tab
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("raw-logs");
|
||||
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy();
|
||||
expect(container.querySelector(".detail-activity")).toBeNull();
|
||||
|
||||
// Switch back to Feed segment within Activity tab.
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
expect(container.querySelector(".detail-activity")).toBeTruthy();
|
||||
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeNull();
|
||||
|
||||
@@ -722,8 +732,8 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Click Activity tab, then Raw Activity segment
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("feed");
|
||||
selectActivityView("raw-logs");
|
||||
|
||||
// Agent log viewer should appear
|
||||
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy();
|
||||
@@ -761,12 +771,12 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Select Activity and Feed — Raw Logs fetching stays disabled
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
const afterLogsClick = mockUseAgentLogs.mock.calls[mockUseAgentLogs.mock.calls.length - 1];
|
||||
expect(afterLogsClick[1]).toBe(false);
|
||||
|
||||
// Switch to Raw Activity segment — enabled should become true
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("raw-logs");
|
||||
const afterAgentLog = mockUseAgentLogs.mock.calls[mockUseAgentLogs.mock.calls.length - 1];
|
||||
expect(afterAgentLog[1]).toBe(true);
|
||||
});
|
||||
@@ -941,20 +951,20 @@ describe("TaskDetailModal", () => {
|
||||
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
expect(screen.getByRole("tab", { name: "Live" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("current");
|
||||
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Expand activity to full modal");
|
||||
|
||||
fireEvent.click(screen.getByTestId("task-chat-expand-toggle"));
|
||||
expect(content).toHaveClass("task-detail-content--chat-expanded");
|
||||
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Collapse activity");
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
expect(content).toHaveClass("task-detail-content--chat-expanded");
|
||||
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-pressed", "true");
|
||||
expect(screen.getByText("Expanded feed entry")).toBeInTheDocument();
|
||||
expect(container.querySelector(".detail-activity-list")).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
selectActivityView("raw-logs");
|
||||
expect(content).toHaveClass("task-detail-content--chat-expanded");
|
||||
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-pressed", "true");
|
||||
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy();
|
||||
@@ -989,7 +999,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
expect(container.querySelector(".task-detail-content")).not.toHaveClass("task-detail-content--chat-expanded");
|
||||
expect(screen.getByRole("tab", { name: "Feed" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("feed");
|
||||
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Expand activity to full modal");
|
||||
});
|
||||
|
||||
@@ -1177,7 +1187,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Activity" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.getByRole("tab", { name: "Feed" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("feed");
|
||||
expect(container.querySelector(".detail-tabs .detail-tab:first-child")).toHaveTextContent("Chat");
|
||||
expect(container.querySelector(".detail-section--chat")).toBeNull();
|
||||
expect(container.querySelector(".detail-activity")).toBeTruthy();
|
||||
@@ -1238,8 +1248,8 @@ describe("TaskDetailModal", () => {
|
||||
expect(chatBody).not.toHaveClass("detail-body--agent-log");
|
||||
expect(chatSection).toBeTruthy();
|
||||
expect(chatSection!.querySelector("[data-testid='task-chat-tab']")).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("feed");
|
||||
selectActivityView("raw-logs");
|
||||
expect(container.querySelector(".detail-body--chat")).toBeNull();
|
||||
expect(container.querySelector(".detail-section--chat")).toBeNull();
|
||||
expect(container.querySelector(".detail-body--agent-log")).toBeTruthy();
|
||||
@@ -1286,10 +1296,10 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Switch to Activity tab, then Raw Activity segment
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
selectActivityView("feed");
|
||||
expect(container.querySelector(".detail-body--agent-log")).toBeNull(); // Feed segment default
|
||||
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("raw-logs");
|
||||
|
||||
// detail-body should now have the agent-log modifier class
|
||||
expect(container.querySelector(".detail-body--agent-log")).toBeTruthy();
|
||||
@@ -1316,8 +1326,8 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Switch to Activity tab, then Raw Activity segment
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("feed");
|
||||
selectActivityView("raw-logs");
|
||||
|
||||
// The section wrapping AgentLogViewer should have the full-height class
|
||||
const section = container.querySelector(".detail-section--agent-log");
|
||||
@@ -1340,8 +1350,8 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Switch to Activity tab, then Raw Activity segment first
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
fireEvent.click(screen.getByText("Raw Logs"));
|
||||
selectActivityView("feed");
|
||||
selectActivityView("raw-logs");
|
||||
expect(container.querySelector(".detail-body--agent-log")).toBeTruthy();
|
||||
|
||||
// Now enter edit mode via the pencil button in the header
|
||||
|
||||
@@ -16,13 +16,13 @@ describe("TaskDetailModal CSS contract", () => {
|
||||
expect(css).toMatch(/\.detail-tab\s*\{[^}]*flex-shrink\s*:\s*0\s*;/);
|
||||
});
|
||||
|
||||
it("FN-7307 keeps Activity segments reachable on narrow task-detail surfaces", async () => {
|
||||
it("FN-7344 keeps the Activity dropdown reachable on narrow task-detail surfaces", async () => {
|
||||
const css = await loadAllAppCssBaseOnly();
|
||||
|
||||
expect(css).toMatch(/\.activity-segmented-control\s*\{[^}]*max-inline-size\s*:\s*100%\s*;/);
|
||||
expect(css).toMatch(/\.activity-segmented-control\s*\{[^}]*overflow-x\s*:\s*auto\s*;/);
|
||||
expect(css).toMatch(/\.activity-segmented-control\s*\{[^}]*touch-action\s*:\s*pan-x\s+pan-y\s*;/);
|
||||
expect(css).toMatch(/\.activity-segment\s*\{[^}]*flex\s*:\s*0\s+0\s+auto\s*;/);
|
||||
expect(css).toMatch(/\.activity-view-select\s*\{[^}]*min-inline-size\s*:\s*calc\(var\(--space-2xl\) \+ var\(--space-xl\) \+ var\(--space-lg\)\)\s*;/);
|
||||
expect(css).toMatch(/\.activity-view-select\s*\{[^}]*min-block-size\s*:\s*var\(--space-2xl\)\s*;/);
|
||||
expect(css).not.toContain(".activity-segmented-control");
|
||||
expect(css).not.toContain(".activity-segment");
|
||||
expect(css).not.toContain(".log-subview-toggle");
|
||||
expect(css).not.toContain(".log-subview-btn");
|
||||
});
|
||||
|
||||
@@ -2254,7 +2254,7 @@ describe("TaskDetailModal", () => {
|
||||
}, { timeout: 3000 });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
fireEvent.change(screen.getByRole("combobox", { name: "Activity view" }), { target: { value: "feed" } });
|
||||
|
||||
const activityList = container.querySelector(".detail-activity-list");
|
||||
expect(activityList).toBeTruthy();
|
||||
|
||||
@@ -23,6 +23,10 @@ import { TaskDetailModal, TaskDetailContent } from "../TaskDetailModal";
|
||||
|
||||
setupTaskDetailModalHooks();
|
||||
|
||||
function selectActivityView(value: "current" | "feed" | "raw-logs") {
|
||||
fireEvent.change(screen.getByRole("combobox", { name: "Activity view" }), { target: { value } });
|
||||
}
|
||||
|
||||
describe("TaskDetailModal", () => {
|
||||
describe("Raw Logs model resolution", () => {
|
||||
// AgentLogViewer only renders the model header when entries.length > 0,
|
||||
@@ -101,7 +105,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
async function openAgentLogAndExpandModelDetails(container: HTMLElement) {
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
selectActivityView("raw-logs");
|
||||
|
||||
await waitFor(() => {
|
||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||
@@ -575,7 +579,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
selectActivityView("raw-logs");
|
||||
await waitFor(() => {
|
||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||
expect(header).toBeTruthy();
|
||||
@@ -632,7 +636,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
selectActivityView("raw-logs");
|
||||
await waitFor(() => {
|
||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||
expect(header).toBeTruthy();
|
||||
@@ -896,7 +900,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Switch to Activity tab, then Raw Logs segment
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
selectActivityView("raw-logs");
|
||||
|
||||
// Should not be visible in Raw Logs segment
|
||||
expect(container.querySelector(".detail-step-progress")).toBeNull();
|
||||
|
||||
@@ -2429,7 +2429,7 @@ describe("TaskDetailModal", () => {
|
||||
}, { timeout: 3000 });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
fireEvent.change(screen.getByRole("combobox", { name: "Activity view" }), { target: { value: "feed" } });
|
||||
|
||||
const activityList = container.querySelector(".detail-activity-list");
|
||||
expect(activityList).toBeTruthy();
|
||||
|
||||
@@ -308,34 +308,23 @@ describe("TaskDetailModal", () => {
|
||||
expect(detailBodyBlock).not.toContain("overflow: hidden;");
|
||||
});
|
||||
|
||||
it("keeps Activity segment tabs equal-height and horizontally reachable", () => {
|
||||
it("keeps the Activity dropdown compact and reachable on mobile", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
const controlBlock = getExactCssRuleBlock(css, ".activity-segmented-control");
|
||||
const segmentBlock = getExactCssRuleBlock(css, ".activity-segment");
|
||||
const activeSegmentBlock = getExactCssRuleBlock(css, ".activity-segment-active");
|
||||
const mobileBlock = getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".activity-segmented-control");
|
||||
const mobileControlBlock = getExactCssRuleBlock(mobileBlock, ".activity-segmented-control");
|
||||
const selectorBlock = getExactCssRuleBlock(css, ".activity-view-select");
|
||||
const mobileBlock = getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".activity-view-select");
|
||||
const mobileSelectorBlock = getExactCssRuleBlock(mobileBlock, ".activity-view-select");
|
||||
|
||||
expectHorizontalTabScroller(controlBlock, "base .activity-segmented-control");
|
||||
expectTabTouchAction(segmentBlock, "base .activity-segment");
|
||||
expect(segmentBlock).toContain("box-sizing: border-box;");
|
||||
expect(segmentBlock).toContain("flex: 0 0 auto;");
|
||||
expect(segmentBlock).toContain("min-block-size: var(--space-2xl);");
|
||||
expect(segmentBlock).toContain("font-size: var(--font-size-xs);");
|
||||
expect(segmentBlock).toContain("line-height: var(--line-height-tight);");
|
||||
expect(segmentBlock).toContain("padding: calc(var(--space-xs) / 2) var(--space-md);");
|
||||
expect(segmentBlock).toContain("border: none;");
|
||||
expect(activeSegmentBlock).toContain("color: var(--text);");
|
||||
expect(activeSegmentBlock).toContain("background: var(--surface);");
|
||||
expect(activeSegmentBlock).toContain("box-shadow: var(--shadow-sm);");
|
||||
expect(activeSegmentBlock).not.toContain("padding:");
|
||||
expect(activeSegmentBlock).not.toContain("min-block-size:");
|
||||
expect(activeSegmentBlock).not.toContain("line-height:");
|
||||
expect(activeSegmentBlock).not.toContain("font-size:");
|
||||
expect(activeSegmentBlock).not.toContain("font-weight:");
|
||||
expect(activeSegmentBlock).not.toContain("border:");
|
||||
expect(mobileControlBlock).toContain("flex: 1 1 auto;");
|
||||
expect(css).not.toMatch(/\.task-detail-content--embedded\s+\.activity-segment(?:ed-control)?\s*\{/);
|
||||
expect(selectorBlock).toContain("min-inline-size: calc(var(--space-2xl) + var(--space-xl) + var(--space-lg));");
|
||||
expect(selectorBlock).toContain("min-block-size: var(--space-2xl);");
|
||||
expect(selectorBlock).toContain("padding-block: calc(var(--space-xs) / 2);");
|
||||
expect(selectorBlock).toContain("padding-inline: var(--space-sm) var(--space-xl);");
|
||||
expect(selectorBlock).toContain("font-size: var(--font-size-sm);");
|
||||
expect(selectorBlock).toContain("line-height: var(--line-height-tight);");
|
||||
expect(mobileSelectorBlock).toContain("flex: 1 1 auto;");
|
||||
expect(mobileSelectorBlock).toContain("min-inline-size: 0;");
|
||||
expect(css).not.toContain(".activity-segmented-control");
|
||||
expect(css).not.toContain(".activity-segment");
|
||||
expect(css).not.toMatch(/\.task-detail-content--embedded\s+\.activity-view-select\s*\{/);
|
||||
});
|
||||
|
||||
it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React, { type ComponentProps } from "react";
|
||||
import type { AgentLogEntry } from "@fusion/core";
|
||||
@@ -49,8 +49,12 @@ function topLevelTabLabels(): string[] {
|
||||
.map((button) => button.textContent?.trim() ?? "");
|
||||
}
|
||||
|
||||
function activitySegmentButtons(): HTMLButtonElement[] {
|
||||
return screen.getAllByRole("tab", { name: /^(Live|Feed|Raw Logs)$/ }) as HTMLButtonElement[];
|
||||
function activitySelector(): HTMLSelectElement {
|
||||
return screen.getByRole("combobox", { name: "Activity view" }) as HTMLSelectElement;
|
||||
}
|
||||
|
||||
function activitySelectorLabels(): string[] {
|
||||
return Array.from(activitySelector().options).map((option) => option.textContent?.trim() ?? "");
|
||||
}
|
||||
|
||||
function mockRawLogs(entries: AgentLogEntry[]) {
|
||||
@@ -66,7 +70,7 @@ function mockRawLogs(entries: AgentLogEntry[]) {
|
||||
}
|
||||
|
||||
describe("TaskDetailModal Activity and planner Chat tab integration", () => {
|
||||
it("defaults to Activity first while segmenting Live, Feed, and Raw Logs without duplicate panels on desktop", async () => {
|
||||
it("defaults to Activity first while selecting Live, Feed, and Raw without duplicate panels on desktop", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRawLogs([
|
||||
{ timestamp: "2026-06-30T20:03:00.000Z", taskId: "FN-7315", type: "text", agent: "executor", text: "raw executor line" },
|
||||
@@ -78,9 +82,11 @@ describe("TaskDetailModal Activity and planner Chat tab integration", () => {
|
||||
expect(screen.getAllByRole("button", { name: "Chat" })).toHaveLength(1);
|
||||
expect(screen.getByRole("button", { name: "Activity" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.queryByTestId("task-planner-chat-panel")).not.toBeInTheDocument();
|
||||
expect(activitySegmentButtons().map((button) => button.textContent?.trim())).toEqual(["Live", "Feed", "Raw Logs"]);
|
||||
expect(activitySegmentButtons().every((button) => (button.textContent ?? "").trim().length > 0)).toBe(true);
|
||||
expect(screen.getByRole("tab", { name: "Live" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelectorLabels()).toEqual(["Live", "Feed", "Raw"]);
|
||||
expect(activitySelector().value).toBe("current");
|
||||
expect(document.querySelector(".activity-segmented-control")).toBeNull();
|
||||
expect(document.querySelector(".activity-segment")).toBeNull();
|
||||
expect(screen.queryByRole("tablist", { name: "Activity views" })).not.toBeInTheDocument();
|
||||
expect(screen.getAllByRole("form", { name: "Task activity composer" })).toHaveLength(1);
|
||||
expect(screen.queryByText(/^Steering comment$/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Send operational guidance to the active task through steering comments.")).not.toBeInTheDocument();
|
||||
@@ -88,18 +94,18 @@ describe("TaskDetailModal Activity and planner Chat tab integration", () => {
|
||||
expect(screen.queryByRole("heading", { name: "Feed" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-log-viewer")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
await user.selectOptions(activitySelector(), "feed");
|
||||
|
||||
expect(screen.getByRole("tab", { name: "Feed" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("feed");
|
||||
expect(screen.queryByRole("form", { name: "Task activity composer" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Feed" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Posted update")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Existing steering guidance")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-log-viewer")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
await user.selectOptions(activitySelector(), "raw-logs");
|
||||
|
||||
expect(screen.getByRole("tab", { name: "Raw Logs" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("raw-logs");
|
||||
expect(screen.queryByRole("form", { name: "Task activity composer" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("heading", { name: "Feed" })).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("agent-log-viewer")).toBeInTheDocument();
|
||||
@@ -124,7 +130,7 @@ describe("TaskDetailModal Activity and planner Chat tab integration", () => {
|
||||
|
||||
expect(topLevelTabLabels().slice(0, 2)).toEqual(["Chat", "Activity"]);
|
||||
expect(screen.getByRole("button", { name: "Activity" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.getByRole("tab", { name: "Live" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("current");
|
||||
expect(screen.queryByTestId("task-planner-chat-panel")).not.toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
@@ -161,7 +167,7 @@ describe("TaskDetailModal Activity and planner Chat tab integration", () => {
|
||||
|
||||
expect(topLevelTabLabels().slice(0, 2)).toEqual(["Chat", "Activity"]);
|
||||
expect(screen.getByRole("button", { name: "Activity" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.getByRole("tab", { name: "Feed" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(activitySelector().value).toBe("feed");
|
||||
expect(screen.getByRole("heading", { name: "Feed" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -188,12 +194,11 @@ describe("TaskDetailModal Activity and planner Chat tab integration", () => {
|
||||
|
||||
expect(topLevelTabLabels().slice(0, 3)).toEqual(["Activity", "Chat", "Summary"]);
|
||||
expect(screen.getByRole("button", { name: "Summary" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.queryByRole("tab", { name: "Live" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("combobox", { name: "Activity view" })).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Activity" }));
|
||||
const segmentGroup = screen.getByRole("tablist", { name: "Activity views" });
|
||||
expect(within(segmentGroup).getAllByRole("tab").map((tab) => tab.textContent?.trim())).toEqual(["Live", "Feed", "Raw Logs"]);
|
||||
expect(within(segmentGroup).getAllByRole("tab")).toHaveLength(3);
|
||||
expect(activitySelectorLabels()).toEqual(["Live", "Feed", "Raw"]);
|
||||
expect(screen.queryByRole("tablist", { name: "Activity views" })).not.toBeInTheDocument();
|
||||
expect(screen.getByText("No agent output yet. Live messages from Planner, Executor, Reviewer, and Merger agents will appear here.")).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("form", { name: "Task refinement composer" })).toHaveLength(1);
|
||||
expect(screen.queryByRole("form", { name: "Refinement request" })).not.toBeInTheDocument();
|
||||
|
||||
@@ -35,6 +35,10 @@ vi.mock("../BranchGroupCard", () => ({
|
||||
|
||||
setupTaskDetailModalHooks();
|
||||
|
||||
async function selectActivityView(user: ReturnType<typeof userEvent.setup>, value: "current" | "feed" | "raw-logs") {
|
||||
await user.selectOptions(screen.getByRole("combobox", { name: "Activity view" }), value);
|
||||
}
|
||||
|
||||
function renderSummarizeTitleModal(overrides: Parameters<typeof makeTask>[0] = {}, props: Partial<ComponentProps<typeof TaskDetailModal>> = {}) {
|
||||
const addToast = props.addToast ?? vi.fn();
|
||||
const onTaskUpdated = props.onTaskUpdated ?? vi.fn();
|
||||
@@ -123,7 +127,7 @@ describe("TaskDetailModal planner Chat tab", () => {
|
||||
renderTask("in-progress", "chat");
|
||||
|
||||
expect(screen.getByRole("button", { name: "Activity" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.getByRole("tab", { name: "Live" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("combobox", { name: "Activity view" })).toHaveValue("current");
|
||||
});
|
||||
|
||||
it("routes explicit planner-chat requests to the new Chat tab", () => {
|
||||
@@ -461,7 +465,7 @@ describe("TaskDetailModal Activity feed loading", () => {
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Activity" }));
|
||||
await user.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
await selectActivityView(user, "feed");
|
||||
expect(await screen.findByRole("status")).toHaveTextContent("Loading activity…");
|
||||
expect(screen.queryByText("(no activity)")).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -542,20 +546,20 @@ describe("TaskDetailModal Chat task merge", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("tab", { name: "Live" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("combobox", { name: "Activity view" })).toHaveValue("current");
|
||||
expect(screen.getAllByRole("form", { name: "Task activity composer" })).toHaveLength(1);
|
||||
expect(screen.queryByText(/^Steering comment$/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Send operational guidance to the active task through steering comments.")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
await selectActivityView(user, "feed");
|
||||
expect(screen.queryByRole("form", { name: "Task activity composer" })).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Started work")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
await selectActivityView(user, "raw-logs");
|
||||
expect(screen.queryByRole("form", { name: "Task activity composer" })).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("agent-log-viewer")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Live" }));
|
||||
await selectActivityView(user, "current");
|
||||
const input = screen.getByLabelText("Message active agent session");
|
||||
await user.type(input, "Please keep the current approach");
|
||||
await user.click(screen.getByRole("button", { name: "Send" }));
|
||||
@@ -586,11 +590,11 @@ describe("TaskDetailModal Chat task merge", () => {
|
||||
expect(screen.getAllByRole("form", { name: "Task activity composer" })).toHaveLength(1);
|
||||
expect(screen.getByText("No agent output yet. Live messages from Planner, Executor, Reviewer, and Merger agents will appear here.")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
await selectActivityView(user, "feed");
|
||||
expect(screen.queryByRole("form", { name: "Task activity composer" })).not.toBeInTheDocument();
|
||||
expect(screen.getByText("(no activity)")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
await selectActivityView(user, "raw-logs");
|
||||
expect(screen.queryByRole("form", { name: "Task activity composer" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -679,8 +683,8 @@ describe("TaskDetailModal Raw Logs agent loading", () => {
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Activity" }));
|
||||
await user.click(screen.getByRole("tab", { name: "Feed" }));
|
||||
await user.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
await selectActivityView(user, "feed");
|
||||
await selectActivityView(user, "raw-logs");
|
||||
|
||||
expect(screen.getByText("Loading agent logs…")).toBeInTheDocument();
|
||||
expect(screen.queryByText("No agent output yet.")).not.toBeInTheDocument();
|
||||
@@ -720,7 +724,7 @@ describe("TaskDetailModal Raw Logs agent loading", () => {
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Activity" }));
|
||||
await user.click(screen.getByRole("tab", { name: "Raw Logs" }));
|
||||
await selectActivityView(user, "raw-logs");
|
||||
|
||||
expect(screen.getByTestId("agent-log-viewer")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("agent-log-summary")).toHaveTextContent("Showing 2 of 5 entries");
|
||||
@@ -974,7 +978,7 @@ describe("TaskDetailModal in-review stall diagnostics", () => {
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "View activity log" }));
|
||||
expect(screen.getByRole("button", { name: "Activity" })).toHaveClass("detail-tab-active");
|
||||
expect(screen.getByRole("tab", { name: "Feed" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("combobox", { name: "Activity view" })).toHaveValue("feed");
|
||||
const highlighted = document.querySelector(".detail-log-entry--stall-highlight .detail-log-action");
|
||||
expect(highlighted?.textContent).toContain("In-review stall surfaced [merge-blocker]");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user