FN-8679: contain mobile Summary table overflow
Keep Summary token and cost tables horizontally scrollable within mobile task details. - Constrain Summary flex ancestors so wide tables cannot widen the detail body - Preserve real token/cost table rendering across populated and empty usage states - Add a patch changeset for the mobile overflow fix Files changed: .changeset/fn-8679-mobile-token-scroll.md | 7 +++ .../dashboard/app/components/TaskDetailModal.css | 9 ++++ .../__tests__/TaskDetailModal.summary-tab.test.tsx | 60 +++++++++++++++++++++- 3 files changed, 75 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-8679 Fusion-Task-Lineage: 33562f89-053f-4681-b1b1-98aa2f2704a2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8679-mobile-token-scroll.md
Normal file
7
.changeset/fn-8679-mobile-token-scroll.md
Normal file
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(<TaskSummaryTab task={doneTask({ tokenUsage: twoModelUsage })} />);
|
||||
const section = screen.getByTestId("task-summary-token-cost-section");
|
||||
const wrapper = section.querySelector<HTMLElement>(".task-summary-token-table-wrap");
|
||||
const table = wrapper?.querySelector<HTMLTableElement>("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(<TaskSummaryTab task={doneTask({ tokenUsage: { ...twoModelUsage, perModel: [twoModelUsage.perModel![0]] } })} />);
|
||||
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(<TaskSummaryTab task={doneTask({ tokenUsage: undefined })} />);
|
||||
expect(screen.queryByTestId("task-summary-token-cost-section")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders multi-model token counts with priced, unpriced, and unavailable total cost states", () => {
|
||||
render(
|
||||
<TaskSummaryTab
|
||||
@@ -450,7 +508,7 @@ describe("TaskDetailModal Summary tab", () => {
|
||||
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}/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user