diff --git a/.changeset/fn-8679-mobile-token-scroll.md b/.changeset/fn-8679-mobile-token-scroll.md
new file mode 100644
index 0000000000..45377a0352
--- /dev/null
+++ b/.changeset/fn-8679-mobile-token-scroll.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Keep Summary token and cost tables scrollable on mobile.
+category: fix
+dev: Contains mobile table overflow within the task Summary surface.
diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css
index 0115856b14..9616aa137b 100644
--- a/packages/dashboard/app/components/TaskDetailModal.css
+++ b/packages/dashboard/app/components/TaskDetailModal.css
@@ -2383,12 +2383,16 @@ The done-task Summary tab is a scrollable overview composed from existing detail
display: flex;
flex-direction: column;
gap: var(--space-lg);
+ min-width: 0;
+ max-width: 100%;
}
.task-summary-section {
display: flex;
flex-direction: column;
gap: var(--space-sm);
+ min-width: 0;
+ max-width: 100%;
padding: var(--space-lg);
background: var(--card);
border: var(--btn-border-width) solid var(--border);
@@ -2467,9 +2471,14 @@ The desktop table needs a token-scale min-width and non-anywhere model-name wrap
FNXC:TaskDetailSummaryTokenCost 2026-07-11-00:00:
Mobile per-model breakdowns must stay real tables and scroll horizontally inside .task-summary-token-table-wrap, matching the Command Center .cc-table-wrap pattern. Do not stack rows into cards; side-by-side model token/cost columns are the user-facing scan path.
+
+FNXC:TaskDetailSummaryTab 2026-08-01-07:15:
+Mobile token/cost scrolling requires min-width: 0 containment through the Summary flex ancestors. Overflow-x alone cannot shrink an intrinsically wide table's flex/grid child, so the wrapper must own horizontal overflow rather than widening the detail body.
*/
.task-summary-token-table-wrap {
width: 100%;
+ min-width: 0;
+ max-width: 100%;
overflow-x: auto;
}
diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx
index 1bb86579ed..cd6243be17 100644
--- a/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx
@@ -190,6 +190,64 @@ describe("TaskDetailModal Summary tab", () => {
expect(screen.queryByTestId("task-summary-token-cost-section")).toBeNull();
});
+ it("keeps populated token costs in a locally scrollable real table across data states", () => {
+ const twoModelUsage = tokenUsage({
+ inputTokens: 300,
+ outputTokens: 150,
+ totalTokens: 450,
+ perModel: [
+ {
+ modelProvider: "anthropic",
+ modelId: "claude-sonnet-4-6",
+ inputTokens: 200,
+ outputTokens: 100,
+ cachedTokens: 0,
+ cacheWriteTokens: 0,
+ totalTokens: 300,
+ firstUsedAt: "2026-01-01T00:00:00Z",
+ lastUsedAt: "2026-01-01T00:00:00Z",
+ },
+ {
+ modelProvider: "openai",
+ modelId: "gpt-4o-mini",
+ inputTokens: 100,
+ outputTokens: 50,
+ cachedTokens: 0,
+ cacheWriteTokens: 0,
+ totalTokens: 150,
+ firstUsedAt: "2026-01-01T00:00:00Z",
+ lastUsedAt: "2026-01-01T00:00:00Z",
+ },
+ ],
+ });
+ const view = render();
+ const section = screen.getByTestId("task-summary-token-cost-section");
+ const wrapper = section.querySelector(".task-summary-token-table-wrap");
+ const table = wrapper?.querySelector("table.task-summary-token-table");
+
+ expect(wrapper).toBeTruthy();
+ expect(table).toBeTruthy();
+ expect(table?.tagName).toBe("TABLE");
+ expect(wrapper?.contains(table ?? null)).toBe(true);
+ for (const heading of ["Model", "Input", "Output", "Cached", "Total", "Cost"]) {
+ expect(within(table!).getByRole("columnheader", { name: heading })).toBeTruthy();
+ }
+ expect(table?.querySelector("tfoot")).toBeTruthy();
+ expect(screen.getAllByTestId("task-summary-token-row")).toHaveLength(2);
+
+ const css = readDashboardStylesSource();
+ expect(css).toMatch(/\.task-summary-tab\s*\{[^}]*min-width:\s*0;[^}]*max-width:\s*100%;/s);
+ expect(css).toMatch(/\.task-summary-section\s*\{[^}]*min-width:\s*0;[^}]*max-width:\s*100%;/s);
+ expect(css).toMatch(/\.task-summary-token-table-wrap\s*\{[^}]*width:\s*100%;[^}]*min-width:\s*0;[^}]*max-width:\s*100%;[^}]*overflow-x:\s*auto;/s);
+
+ view.rerender();
+ expect(screen.getAllByTestId("task-summary-token-row")).toHaveLength(1);
+ expect(screen.getByTestId("task-summary-token-cost-section").querySelector("table.task-summary-token-table")).toBeTruthy();
+
+ view.rerender();
+ expect(screen.queryByTestId("task-summary-token-cost-section")).toBeNull();
+ });
+
it("renders multi-model token counts with priced, unpriced, and unavailable total cost states", () => {
render(
{
expect(mobileTokenBlock).not.toContain(".task-summary-token-table-wrap");
expect(mobileTokenBlock).not.toContain("overflow-x: visible");
expect(mobileTokenBlock).not.toContain(".task-summary-token-table td::before");
- expect(mobileTokenBlock).not.toContain("min-width: 0");
+ expect(tokenTableRule).not.toContain("min-width: 0");
expect(css).toContain("var(--color-warning)");
expect(css).not.toMatch(/task-summary-token[^{}]*#[0-9a-fA-F]{3,8}/);
});