diff --git a/.changeset/fn-8935-agents-overview-mobile-scroll.md b/.changeset/fn-8935-agents-overview-mobile-scroll.md new file mode 100644 index 0000000000..7d9ebcf5a4 --- /dev/null +++ b/.changeset/fn-8935-agents-overview-mobile-scroll.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Make the Agents Overview Active Agents list scrollable on mobile. +category: fix +dev: Overview bar now participates in the Agents flex height chain with a touch scroll owner so long active-agent lists are not clipped; covered by a Chromium browser-layout smoke assertion mirroring the production DOM chain. diff --git a/docs/testing.md b/docs/testing.md index a4be4ad722..6fab94fd91 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -202,6 +202,8 @@ The dashboard CSS contract lane includes `app/__tests__/dashboard-css-token-vali Command Center responsive chart fixes need evidence beyond jsdom. Keep the jsdom scroll-owner tests for rule/structure coverage, but pair them with `packages/dashboard/app/components/command-center/__tests__/CommandCenter.mobile-chart-layout.test.ts`, which reads the co-located Command Center CSS files directly and asserts the mobile shrink/height/border rules that real layout depends on. For visible defects, also capture a real browser/device (or headless Chrome/Blink) reproduction with `scrollWidth > clientWidth`, zero/clipped `clientHeight`, or stretch measurements; do not close a Command Center mobile chart bug on jsdom-green assertions alone. The local `pnpm --filter @fusion/dashboard test:browser-smoke --require-browser` lane now includes `[data-smoke="command-center-charts"]` and gates representative Command Center recharts pie, line, and empty states at 390×844 mobile plus desktop viewports for visible SVG/container height, overflow containment, empty-state text, and chart scroll-owner violations. +The same required-browser lane also measures the Agents Overview fixture at 390×844 mobile and 1280×700 short-desktop viewports. It verifies the real Active Agents scroll owner overflows, reaches the final card after scrolling, preserves sibling Agents content, avoids horizontal page overflow, and leaves the metrics-only empty state unclipped. + The shared mobile/tablet overflow-containment net lives at `packages/dashboard/app/__tests__/dashboard-overflow-containment.test.tsx`. It covers board/kanban columns, task-detail modal shell, workflow/simple workflow editors, and Activity Log modal at mobile, tablet, and landscape-phone breakpoints. Run it directly when touching dashboard viewport containment or shared modal/workflow CSS: ```bash diff --git a/packages/dashboard/app/__tests__/browser-layout-smoke-fixture.test.ts b/packages/dashboard/app/__tests__/browser-layout-smoke-fixture.test.ts index b3022262e9..7132df008d 100644 --- a/packages/dashboard/app/__tests__/browser-layout-smoke-fixture.test.ts +++ b/packages/dashboard/app/__tests__/browser-layout-smoke-fixture.test.ts @@ -121,6 +121,30 @@ describe("browser layout smoke fixture", () => { expect(html).toContain("Plan Review needs approval"); }); + it("includes the production Agents Overview scroll chain", () => { + const html = createSmokeHtml(); + for (const hook of [ + "agents-overview-scroll", + "show-agents-overview-scroll", + "agents-overview-scroll-owner", + "agents-overview-last-card", + "agents-overview-scroll-empty", + "agents-overview-empty-scroll-owner", + ]) { + expect(html).toContain(`data-smoke="${hook}"`); + } + for (const className of [ + "agents-overview-bar__content", + "agent-metrics-bar", + "active-agents-panel", + "active-agents-grid", + "live-agent-card", + ]) { + expect(html).toContain(className); + } + expect(html.match(/class="live-agent-card"/g)).toHaveLength(13); + }); + it("includes PR flow fixture sections and class hooks", () => { const html = createSmokeHtml(); expect(html).toContain('data-smoke="pr-create-modal"'); @@ -158,7 +182,7 @@ describe("browser layout smoke fixture", () => { expect(html).toContain('data-smoke="quick-add-save-row"'); expect(html).toContain('data-smoke="quick-add-save-button"'); expect(html).toContain('data-testid="quick-entry-session-advisor-toggle"'); - expect(html.match(/data-testid="quick-entry-(?:attach|github-toggle|session-advisor-toggle|priority-button|fast-toggle)"/g)).toHaveLength(120); + expect(html.match(/data-testid="quick-entry-(?:attach|github-toggle|session-advisor-toggle|priority-button|fast-toggle)"/g)).toHaveLength(140); for (const label of ["Save", "Guardar", "Enregistrer", "저장", "保存", "儲存"]) { expect(html).toContain(label); } diff --git a/packages/dashboard/app/components/AgentsOverviewBar.css b/packages/dashboard/app/components/AgentsOverviewBar.css index cdc6e3340a..c57d6763d1 100644 --- a/packages/dashboard/app/components/AgentsOverviewBar.css +++ b/packages/dashboard/app/components/AgentsOverviewBar.css @@ -1,14 +1,23 @@ +/* +FNXC:AgentsOverviewScroll 2026-08-10-10:02: +Overview owns vertical scrolling when Active Agents exceeds the constrained Agents flex shell, so mobile and short viewports can reach every card without restoring document scrolling. The overview itself clips its flex child while the content scrollport contains overscroll before nested card transcripts can hand it to the page. +*/ .agents-overview-bar { border-bottom: 1px solid var(--border); background: var(--surface); padding: var(--space-md) var(--space-lg); display: flex; + flex: 0 1 auto; flex-direction: column; + min-height: 0; + max-height: 70%; gap: var(--space-sm); + overflow: hidden; } .agents-overview-bar__toggle { width: 100%; + flex-shrink: 0; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--card); @@ -47,8 +56,13 @@ .agents-overview-bar__content { display: flex; + flex: 1 1 auto; flex-direction: column; + min-height: 0; gap: var(--space-md); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; } .agents-overview-bar__metrics { diff --git a/packages/dashboard/app/components/__tests__/AgentsOverviewBar.mobile-scroll.test.tsx b/packages/dashboard/app/components/__tests__/AgentsOverviewBar.mobile-scroll.test.tsx new file mode 100644 index 0000000000..7958d7b71b --- /dev/null +++ b/packages/dashboard/app/components/__tests__/AgentsOverviewBar.mobile-scroll.test.tsx @@ -0,0 +1,96 @@ +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render } from "@testing-library/react"; +import type { Agent } from "../../api"; +import { loadAllAppCss } from "../../test/cssFixture"; + +vi.mock("../../hooks/useLiveTranscript", () => ({ + useLiveTranscript: () => ({ entries: [], isConnected: false }), +})); +vi.mock("../../hooks/useAgentActivity", () => ({ + useAgentActivity: () => ({ activityByAgentId: new Map(), events: [], nowTick: 0 }), +})); +vi.mock("../RuntimeFallbackBadge", () => ({ RuntimeFallbackBadge: () => null })); +vi.mock("../AgentTaskBadge", () => ({ AgentTaskBadge: () => null })); + +import { AgentsOverviewBar } from "../AgentsOverviewBar"; + +function makeAgent(id: string): Agent { + return { + id, + name: `Agent ${id}`, + role: "executor", + state: "active", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + metadata: {}, + }; +} + +const css = loadAllAppCss(); + +function rule(selector: string): string { + const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return css.match(new RegExp(`${escaped}\\s*\\{([^}]*)\\}`))?.[1] ?? ""; +} + +describe("AgentsOverviewBar mobile scroll contract", () => { + it("keeps the overview content as the constrained touch scroll owner", () => { + const overview = rule(".agents-overview-bar"); + const content = rule(".agents-overview-bar__content"); + + expect(overview).toContain("min-height: 0"); + expect(overview).toContain("overflow: hidden"); + expect(content).toContain("min-height: 0"); + expect(content).toContain("overflow-y: auto"); + expect(content).toContain("-webkit-overflow-scrolling: touch"); + expect(content).toContain("overscroll-behavior: contain"); + expect(rule(".agents-overview-bar__toggle")).toContain("flex-shrink: 0"); + + expect(rule(".agents-view-content")).toContain("flex: 1"); + expect(rule(".agents-view-content")).toContain("min-height: 0"); + expect(rule(".agents-view-content")).toContain("overflow-y: auto"); + expect(rule(".agent-org-chart-viewport")).toContain("overflow: hidden"); + expect(rule(".agent-org-chart-viewport")).toContain("overscroll-behavior: contain"); + }); + + it("renders the production overview chain for many, duplicate, and empty active-agent states", () => { + const agents = Array.from({ length: 13 }, (_, index) => makeAgent(`agent-${index}`)); + const onToggle = vi.fn(); + const { container, rerender } = render( +