FN-7386: overlay Activity expand controls and remove Raw duplicate

Overlay Activity expansion controls while keeping Raw logs to a single fullscreen affordance.

- Move Live and Feed Activity expand controls into overlaid buttons instead of a reserved toolbar row.
- Let Raw logs rely on AgentLogViewer fullscreen controls, including loading and empty states, to avoid duplicate expand buttons.
- Update responsive CSS and regression coverage for Activity overlay behavior and Raw fullscreen uniqueness.
- Add a patch changeset for the dashboard Activity control fix.

Files changed:
 .changeset/fn-7386-activity-expand.md              |  7 ++++
 .../dashboard/app/components/AgentLogViewer.css    |  6 ++++
 .../dashboard/app/components/AgentLogViewer.tsx    | 34 ++++++++++++-------
 packages/dashboard/app/components/TaskChatTab.tsx  |  2 +-
 .../dashboard/app/components/TaskDetailModal.css   | 36 ++++++++++++++------
 .../dashboard/app/components/TaskDetailModal.tsx   | 29 +++++++++--------
 .../TaskDetailModal.attachments-and-tabs.test.tsx  | 38 ++++++++++++++++++----
 ...etailModal.responsive-and-dependencies.test.tsx | 17 ++++++++++
 8 files changed, 128 insertions(+), 41 deletions(-)

Fusion-Task-Id: FN-7386

Fusion-Task-Lineage: 93728fc2-785d-44de-9903-b61df4911053

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-01 11:07:32 -07:00
parent ec0fa960d7
commit 4378053433
8 changed files with 128 additions and 41 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix Activity expand controls so Live and Feed overlay content and Raw has one fullscreen button.
category: fix
dev: Updates task-detail Activity Live/Feed overlay controls and keeps Raw on AgentLogViewer fullscreen.

View File

@@ -42,6 +42,12 @@
background: var(--text-muted); background: var(--text-muted);
} }
.agent-log-empty-header {
display: flex;
justify-content: flex-end;
padding: var(--space-sm) var(--space-md) 0;
}
.agent-log-loading, .agent-log-loading,
.agent-log-empty { .agent-log-empty {
display: flex; display: flex;

View File

@@ -451,6 +451,18 @@ export function AgentLogViewer({
const hasValidatorOverride = validatorModel?.provider && validatorModel?.modelId; const hasValidatorOverride = validatorModel?.provider && validatorModel?.modelId;
const hasPlanningOverride = planningModel?.provider && planningModel?.modelId; const hasPlanningOverride = planningModel?.provider && planningModel?.modelId;
const fullscreenToggle = (
<button
className="agent-log-mode-toggle"
onClick={() => setIsFullscreen((prev) => !prev)}
aria-label={isFullscreen ? t("agentLog.exitFullscreen", "Exit full screen") : t("agentLog.expandFullscreen", "Expand agent log to full screen")}
data-testid="agent-log-fullscreen-toggle"
title={isFullscreen ? t("agentLog.exitFullscreen", "Exit full screen") : t("agentLog.expandFullscreen", "Expand agent log to full screen")}
>
{isFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
</button>
);
const modelProviders = useMemo(() => { const modelProviders = useMemo(() => {
const providers: Array<{ role: string; provider: string; modelId?: string }> = []; const providers: Array<{ role: string; provider: string; modelId?: string }> = [];
if (hasExecutorOverride) { if (hasExecutorOverride) {
@@ -486,7 +498,11 @@ export function AgentLogViewer({
if (loading && entries.length === 0) { if (loading && entries.length === 0) {
return ( return (
<div className="agent-log-viewer" data-testid="agent-log-viewer"> <div className={`agent-log-viewer${isFullscreen ? " agent-log-viewer--fullscreen" : ""}`} data-testid="agent-log-viewer">
{/* FNXC:TaskDetailActivity 2026-07-01-00:00: Activity → Raw owns one fullscreen affordance through AgentLogViewer even while logs are loading, because TaskDetailModal intentionally omits its Activity-level expand button on Raw to avoid duplicate controls. */}
<div className="agent-log-empty-header">
<div className="agent-log-model-header-toggle">{fullscreenToggle}</div>
</div>
<div className="agent-log-loading" role="status" aria-live="polite">{t("agentLog.loading", "Loading agent logs…")}</div> <div className="agent-log-loading" role="status" aria-live="polite">{t("agentLog.loading", "Loading agent logs…")}</div>
</div> </div>
); );
@@ -494,7 +510,11 @@ export function AgentLogViewer({
if (entries.length === 0) { if (entries.length === 0) {
return ( return (
<div className="agent-log-viewer" data-testid="agent-log-viewer"> <div className={`agent-log-viewer${isFullscreen ? " agent-log-viewer--fullscreen" : ""}`} data-testid="agent-log-viewer">
{/* FNXC:TaskDetailActivity 2026-07-01-00:00: Empty Raw logs still expose the single AgentLogViewer fullscreen button so Raw never needs the duplicate Activity expand toggle. */}
<div className="agent-log-empty-header">
<div className="agent-log-model-header-toggle">{fullscreenToggle}</div>
</div>
<div className="agent-log-empty">{t("agentLog.empty", "No agent output yet.")}</div> <div className="agent-log-empty">{t("agentLog.empty", "No agent output yet.")}</div>
</div> </div>
); );
@@ -549,15 +569,7 @@ export function AgentLogViewer({
> >
{showToolOutput ? t("agentLog.toolsOn", "Tools: On") : t("agentLog.toolsOff", "Tools: Off")} {showToolOutput ? t("agentLog.toolsOn", "Tools: On") : t("agentLog.toolsOff", "Tools: Off")}
</button> </button>
<button {fullscreenToggle}
className="agent-log-mode-toggle"
onClick={() => setIsFullscreen((prev) => !prev)}
aria-label={isFullscreen ? t("agentLog.exitFullscreen", "Exit full screen") : t("agentLog.expandFullscreen", "Expand agent log to full screen")}
data-testid="agent-log-fullscreen-toggle"
title={isFullscreen ? t("agentLog.exitFullscreen", "Exit full screen") : t("agentLog.expandFullscreen", "Expand agent log to full screen")}
>
{isFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
</button>
</div> </div>
{modelHeaderExpanded && ( {modelHeaderExpanded && (

View File

@@ -874,7 +874,7 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated,
aria-pressed={expanded} aria-pressed={expanded}
data-testid="task-chat-expand-toggle" data-testid="task-chat-expand-toggle"
> >
{/* FNXC:TaskDetailActivity 2026-06-30-23:55: The legacy TaskChatTab expand affordance now uses Activity-wide copy when a caller still renders it; TaskDetailModal owns the visible Activity-level toggle across Live, Feed, and Raw Logs. */} {/* FNXC:TaskDetailActivity 2026-07-01-00:00: TaskDetailModal passes Activity-expanded state into Live so this existing chat overlay remains the single Live expand affordance without adding a separate toolbar row. */}
{expanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />} {expanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />}
</button> </button>
) : null} ) : null}

View File

@@ -2564,14 +2564,15 @@ The Activity view menu is portaled to the document body and fixed-positioned bec
font-weight: 600; font-weight: 600;
} }
.activity-toolbar { .detail-activity {
display: flex; position: relative;
align-items: flex-start; padding-inline-end: calc(var(--space-2xl) + var(--space-md));
justify-content: flex-end;
gap: var(--space-sm);
margin-bottom: var(--space-md);
} }
/*
FNXC:TaskDetailActivity 2026-07-01-00:00:
Live and Feed Activity expansion overlays the content instead of reserving a toolbar row. Feed keeps enough tokenized inline padding that the fixed affordance does not cover log text; Raw uses AgentLogViewer's fullscreen button and does not render this Activity toggle.
*/
.activity-expand-toggle { .activity-expand-toggle {
flex: 0 0 auto; flex: 0 0 auto;
min-inline-size: var(--space-2xl); min-inline-size: var(--space-2xl);
@@ -2579,6 +2580,16 @@ The Activity view menu is portaled to the document body and fixed-positioned bec
padding: 0; padding: 0;
} }
.activity-expand-toggle--overlay {
position: absolute;
top: var(--space-md);
right: var(--space-md);
z-index: 3;
background: var(--surface);
border-color: var(--border);
box-shadow: var(--shadow-sm);
}
.detail-log-loading { .detail-log-loading {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -2592,19 +2603,24 @@ The Activity view menu is portaled to the document body and fixed-positioned bec
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.activity-toolbar {
align-items: stretch;
}
.activity-view-menu { .activity-view-menu {
max-inline-size: calc(100vw - (var(--space-md) * 2)); max-inline-size: calc(100vw - (var(--space-md) * 2));
} }
.detail-activity {
padding-inline-end: calc(var(--space-2xl) + var(--space-lg));
}
.activity-expand-toggle { .activity-expand-toggle {
min-inline-size: calc(var(--space-2xl) + var(--space-sm)); min-inline-size: calc(var(--space-2xl) + var(--space-sm));
min-block-size: calc(var(--space-2xl) + var(--space-sm)); min-block-size: calc(var(--space-2xl) + var(--space-sm));
} }
.activity-expand-toggle--overlay {
top: var(--space-sm);
right: var(--space-sm);
}
.detail-log-loading { .detail-log-loading {
min-height: calc(var(--space-2xl) + var(--space-lg)); min-height: calc(var(--space-2xl) + var(--space-lg));
} }

View File

@@ -3666,20 +3666,11 @@ export function TaskDetailContent({
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. 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-23:59: FNXC:TaskDetailActivity 2026-06-30-23:59:
The Activity tab in the top-level tab strip is now the view dropdown for Live, Feed, and Raw. The in-panel Activity view select was removed so Activity expansion remains the only toolbar affordance inside the panel while legacy routing and Raw-only fetching keep their stable ids (`chat`, `current`, `feed`, `raw-logs`). The Activity tab in the top-level tab strip is now the view dropdown for Live, Feed, and Raw. The in-panel Activity view select was removed so Activity expansion remains the only Activity-level affordance inside the panel while legacy routing and Raw-only fetching keep their stable ids (`chat`, `current`, `feed`, `raw-logs`).
FNXC:TaskDetailActivity 2026-07-01-00:00:
Activity expansion must not reserve a standalone toolbar row. Live uses TaskChatTab's anchored overlay button, Feed renders the same Activity toggle over its feed panel, and Raw keeps AgentLogViewer's fullscreen control so only one Raw expand affordance is reachable.
*/} */}
<div className="activity-toolbar activity-toolbar--expand-only">
<button
type="button"
className="btn btn-icon btn-sm activity-expand-toggle"
onClick={() => setActivityExpanded((value) => !value)}
aria-label={isActivityExpanded ? t("taskDetail.activity.collapse", "Collapse activity") : t("taskDetail.activity.expand", "Expand activity to full modal")}
aria-pressed={isActivityExpanded}
data-testid="task-chat-expand-toggle"
>
{isActivityExpanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />}
</button>
</div>
{activitySegment === "current" ? ( {activitySegment === "current" ? (
<TaskChatTab <TaskChatTab
task={workingTask} task={workingTask}
@@ -3688,6 +3679,8 @@ export function TaskDetailContent({
addToast={addToast} addToast={addToast}
sessionLive={isCliSessionLive(cliSession)} sessionLive={isCliSessionLive(cliSession)}
onTaskUpdated={handleChatTaskUpdated} onTaskUpdated={handleChatTaskUpdated}
expanded={isActivityExpanded}
onToggleExpanded={() => setActivityExpanded((value) => !value)}
effectiveModels={{ effectiveModels={{
triage: toTaskChatModelInfo(resolveEffectivePlanning(workingTask, agentLogEntries, settings)), triage: toTaskChatModelInfo(resolveEffectivePlanning(workingTask, agentLogEntries, settings)),
executor: toTaskChatModelInfo(resolveEffectiveExecutor(workingTask, agentLogEntries, assignedAgent, settings)), executor: toTaskChatModelInfo(resolveEffectiveExecutor(workingTask, agentLogEntries, assignedAgent, settings)),
@@ -3709,6 +3702,16 @@ export function TaskDetailContent({
/> />
) : ( ) : (
<div className="detail-activity" role="tabpanel"> <div className="detail-activity" role="tabpanel">
<button
type="button"
className="btn btn-icon btn-sm activity-expand-toggle activity-expand-toggle--overlay"
onClick={() => setActivityExpanded((value) => !value)}
aria-label={isActivityExpanded ? t("taskDetail.activity.collapse", "Collapse activity") : t("taskDetail.activity.expand", "Expand activity to full modal")}
aria-pressed={isActivityExpanded}
data-testid="task-chat-expand-toggle"
>
{isActivityExpanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />}
</button>
<h4>{t("taskDetail.activity.feedHeading", "Feed")}</h4> <h4>{t("taskDetail.activity.feedHeading", "Feed")}</h4>
{(workingTask as typeof workingTask & { activityLogTruncatedCount?: number }).activityLogTruncatedCount ? ( {(workingTask as typeof workingTask & { activityLogTruncatedCount?: number }).activityLogTruncatedCount ? (
<div className="detail-log-truncated"> <div className="detail-log-truncated">

View File

@@ -30,7 +30,7 @@ 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. 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: 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`, 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`, Live/Feed Activity expand, and Raw fullscreen) are intentionally mounted only after selecting Activity or using an explicit legacy Activity tab request.
*/ */
setupTaskDetailModalHooks(); setupTaskDetailModalHooks();
@@ -758,8 +758,12 @@ describe("TaskDetailModal", () => {
selectActivityView("feed"); selectActivityView("feed");
selectActivityView("raw-logs"); selectActivityView("raw-logs");
// Agent log viewer should appear // Agent log viewer should appear with one Raw fullscreen affordance and no duplicate Activity expand toggle.
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy(); expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy();
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
expect(screen.getAllByTestId("agent-log-fullscreen-toggle")).toHaveLength(1);
expect(screen.getByTestId("agent-log-fullscreen-toggle")).toHaveAttribute("aria-label", "Expand agent log to full screen");
expect(container.querySelector(".activity-toolbar")).toBeNull();
// Definition content should be hidden // Definition content should be hidden
expect(container.querySelector(".markdown-body")).toBeNull(); expect(container.querySelector(".markdown-body")).toBeNull();
@@ -886,8 +890,11 @@ describe("TaskDetailModal", () => {
expect(mobileSectionRule).toContain("min-height: 0"); expect(mobileSectionRule).toContain("min-height: 0");
}); });
it("FN-6370/FN-7351 defines expanded Activity chrome CSS for desktop and mobile", () => { it("FN-6370/FN-7351/FN-7386 defines expanded Activity chrome and overlay CSS for desktop and mobile", () => {
const css = readDashboardStylesSource(); const css = readDashboardStylesSource();
const activityOverlayRule = getCssRuleBlock(css, ".activity-expand-toggle--overlay");
const mobileCss = css.slice(css.indexOf("@media (max-width: 768px)"));
const expandedTitleRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-title-row"); const expandedTitleRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-title-row");
const expandedMetaRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-meta"); const expandedMetaRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-meta");
const expandedTabsRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-tabs"); const expandedTabsRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-tabs");
@@ -895,11 +902,19 @@ describe("TaskDetailModal", () => {
const expandedHeaderRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .modal-header"); const expandedHeaderRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .modal-header");
const expandedBodyRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-body--chat"); const expandedBodyRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-body--chat");
const expandedSectionRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-section--chat"); const expandedSectionRule = getCssRuleBlock(css, ".task-detail-content--chat-expanded .detail-section--chat");
const mobileCss = css.slice(css.indexOf("@media (max-width: 768px)"));
const mobileTitleRule = getCssRuleBlock(mobileCss, ".task-detail-content--chat-expanded .detail-title-row"); const mobileTitleRule = getCssRuleBlock(mobileCss, ".task-detail-content--chat-expanded .detail-title-row");
const mobileTabsRule = getCssRuleBlock(mobileCss, ".task-detail-content--chat-expanded .detail-tabs"); const mobileTabsRule = getCssRuleBlock(mobileCss, ".task-detail-content--chat-expanded .detail-tabs");
const mobileActionsRule = getCssRuleBlock(mobileCss, ".task-detail-content--chat-expanded .modal-actions"); const mobileActionsRule = getCssRuleBlock(mobileCss, ".task-detail-content--chat-expanded .modal-actions");
expect(css).not.toContain(".activity-toolbar");
expect(css).not.toContain("activity-toolbar--expand-only");
expect(css).toContain(".detail-activity {\n position: relative;\n padding-inline-end: calc(var(--space-2xl) + var(--space-md));\n}");
expect(activityOverlayRule).toContain("position: absolute");
expect(activityOverlayRule).toContain("top: var(--space-md)");
expect(activityOverlayRule).toContain("right: var(--space-md)");
expect(activityOverlayRule).toContain("z-index: 3");
expect(mobileCss).toContain(" .detail-activity {\n padding-inline-end: calc(var(--space-2xl) + var(--space-lg));\n }");
expect(mobileCss).toContain(" .activity-expand-toggle--overlay {\n top: var(--space-sm);\n right: var(--space-sm);\n }");
expect(expandedTitleRule).not.toContain("display: none"); expect(expandedTitleRule).not.toContain("display: none");
expect(expandedMetaRule).toContain("display: none"); expect(expandedMetaRule).toContain("display: none");
expect(expandedTabsRule).toContain("display: flex"); expect(expandedTabsRule).toContain("display: flex");
@@ -929,6 +944,10 @@ describe("TaskDetailModal", () => {
fireEvent.click(screen.getByRole("button", { name: "Activity" })); fireEvent.click(screen.getByRole("button", { name: "Activity" }));
const content = container.querySelector(".task-detail-content"); const content = container.querySelector(".task-detail-content");
const titleRow = container.querySelector(".detail-title-row"); const titleRow = container.querySelector(".detail-title-row");
const liveToggle = screen.getByTestId("task-chat-expand-toggle");
expect(liveToggle).toHaveClass("task-chat-expand-toggle--overlay");
expect(liveToggle.closest(".activity-toolbar")).toBeNull();
expect(container.querySelector(".activity-toolbar")).toBeNull();
expect(content).not.toHaveClass("task-detail-content--chat-expanded"); expect(content).not.toHaveClass("task-detail-content--chat-expanded");
expect(titleRow).toHaveTextContent("FN-099"); expect(titleRow).toHaveTextContent("FN-099");
expect(container.querySelector(".detail-tabs")).toBeTruthy(); expect(container.querySelector(".detail-tabs")).toBeTruthy();
@@ -976,20 +995,27 @@ describe("TaskDetailModal", () => {
fireEvent.click(screen.getByRole("button", { name: "Activity" })); fireEvent.click(screen.getByRole("button", { name: "Activity" }));
expectActivityView("current"); expectActivityView("current");
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Expand activity to full modal"); expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Expand activity to full modal");
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveClass("task-chat-expand-toggle--overlay");
expect(screen.getByTestId("task-chat-expand-toggle").closest(".activity-toolbar")).toBeNull();
expect(container.querySelector(".activity-toolbar")).toBeNull();
fireEvent.click(screen.getByTestId("task-chat-expand-toggle")); fireEvent.click(screen.getByTestId("task-chat-expand-toggle"));
expect(content).toHaveClass("task-detail-content--chat-expanded"); expect(content).toHaveClass("task-detail-content--chat-expanded");
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Collapse activity"); expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-label", "Collapse activity");
selectActivityView("feed"); selectActivityView("feed");
expect(content).toHaveClass("task-detail-content--chat-expanded"); expect(content).toHaveClass("task-detail-content--chat-expanded");
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-pressed", "true"); expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-pressed", "true");
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveClass("activity-expand-toggle--overlay");
expect(screen.getByTestId("task-chat-expand-toggle").closest(".activity-toolbar")).toBeNull();
expect(screen.getByText("Expanded feed entry")).toBeInTheDocument(); expect(screen.getByText("Expanded feed entry")).toBeInTheDocument();
expect(container.querySelector(".detail-activity-list")).toBeTruthy(); expect(container.querySelector(".detail-activity-list")).toBeTruthy();
selectActivityView("raw-logs"); selectActivityView("raw-logs");
expect(content).toHaveClass("task-detail-content--chat-expanded"); expect(content).toHaveClass("task-detail-content--chat-expanded");
expect(screen.getByTestId("task-chat-expand-toggle")).toHaveAttribute("aria-pressed", "true"); expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
expect(screen.getByTestId("agent-log-fullscreen-toggle")).toHaveAttribute("aria-label", "Expand agent log to full screen");
expect(screen.getAllByTestId("agent-log-fullscreen-toggle")).toHaveLength(1);
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy(); expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeTruthy();
}); });

View File

@@ -387,6 +387,23 @@ describe("TaskDetailModal", () => {
expect(css).not.toContain(".activity-segment"); expect(css).not.toContain(".activity-segment");
}); });
it("keeps Activity Live/Feed expand controls overlaid without a mobile toolbar row", () => {
const css = readDashboardStylesSource();
const overlayBlock = getExactCssRuleBlock(css, ".activity-expand-toggle--overlay");
const mobileBlock = getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".activity-expand-toggle--overlay");
const mobileOverlayBlock = getExactCssRuleBlock(mobileBlock, ".activity-expand-toggle--overlay");
expect(css).not.toContain(".activity-toolbar");
expect(css).not.toContain("activity-toolbar--expand-only");
expect(css).toContain(".detail-activity {\n position: relative;\n padding-inline-end: calc(var(--space-2xl) + var(--space-md));\n}");
expect(overlayBlock).toContain("position: absolute;");
expect(overlayBlock).toContain("top: var(--space-md);");
expect(overlayBlock).toContain("right: var(--space-md);");
expect(mobileBlock).toContain(" .detail-activity {\n padding-inline-end: calc(var(--space-2xl) + var(--space-lg));\n }");
expect(mobileOverlayBlock).toContain("top: var(--space-sm);");
expect(mobileOverlayBlock).toContain("right: var(--space-sm);");
});
it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => { it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => {
const { container } = render( const { container } = render(
<TaskDetailModal <TaskDetailModal