Files
fusion/packages/dashboard/app/__tests__/token-table-mobile-scroll.test.ts
gsxdsm be1950b79c FN-7843: keep task-detail per-model token tables horizontally scrollable on mobile
Removes the mobile stacked-card override for the Summary tab's per-model token/cost table so it stays a real, horizontally-scrollable table matching the Command Center pattern, and updates the covering tests/changeset accordingly.

- Drop the @media (max-width: 768px) block in TaskDetailModal.css that converted .task-summary-token-table rows into stacked cards (display:block, thead hidden, td::before labels), so the wrapper's overflow-x: auto and table min-width now govern mobile layout.
- Update FNXC:TaskDetailSummaryTokenCost comments to document the new horizontal-scroll contract instead of the removed stacked-card behavior, and refresh the TaskCostTab.css FNXC comment to match.
- Add a new regression test (token-table-mobile-scroll.test.ts) asserting the task-detail token table wrapper keeps overflow-x: auto, the mobile media block no longer stacks rows into cards, and the Command Center .cc-table-wrap stays scrollable.
- Update the existing TaskDetailModal summary-tab test to assert the stacked-card CSS is absent from the mobile block instead of asserting its presence.
- Add a patch changeset describing the fix for end users.

Files changed:
 .changeset/mobile-token-table-scroll.md            |  7 +++
 .../__tests__/token-table-mobile-scroll.test.ts    | 70 +++++++++++++++++++++
 packages/dashboard/app/components/TaskCostTab.css  |  2 +-
 .../dashboard/app/components/TaskDetailModal.css   | 73 ++--------------------
 .../__tests__/TaskDetailModal.summary-tab.test.tsx | 13 ++--
 5 files changed, 90 insertions(+), 75 deletions(-)

Fusion-Task-Id: FN-7843

Fusion-Task-Lineage: bb6dde7c-f205-45f3-984a-0fc52be5d547

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-11 22:16:48 -07:00

71 lines
2.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";
function readCss(relativePath: string): string {
return readFileSync(resolve(__dirname, relativePath), "utf-8");
}
function extractFirstMediaBlock(css: string, pattern: RegExp): string {
const match = pattern.exec(css);
expect(match, `missing media block for ${pattern}`).toBeTruthy();
const start = match!.index + match![0].length;
let index = start;
let depth = 1;
while (index < css.length && depth > 0) {
if (css[index] === "{") depth += 1;
if (css[index] === "}") depth -= 1;
index += 1;
}
expect(depth).toBe(0);
return css.slice(start, index - 1);
}
function ruleBlock(css: string, selector: string): string {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const match = css.match(new RegExp(`${escaped}\\s*\\{[^}]*\\}`, "s"));
expect(match, `missing CSS rule for ${selector}`).toBeTruthy();
return match![0];
}
describe("FN-7843 token table mobile scroll contract", () => {
const taskDetailCss = readCss("../components/TaskDetailModal.css");
const commandCenterAreasCss = readCss("../components/command-center/areas/areas.css");
const mobileTaskDetailCss = extractFirstMediaBlock(
taskDetailCss,
/@media\s*\(max-width:\s*768px\)\s*\{/,
);
it("keeps the task-detail token table overflow owned by its wrapper", () => {
const wrapperRule = ruleBlock(taskDetailCss, ".task-summary-token-table-wrap");
const tableRule = ruleBlock(taskDetailCss, ".task-summary-token-table");
expect(wrapperRule).toContain("overflow-x: auto;");
expect(tableRule).toMatch(/min-width:\s*[^;]+;/);
});
it("does not convert task-detail token tables into stacked mobile cards", () => {
expect(mobileTaskDetailCss).not.toMatch(
/\.task-summary-token-table-wrap\s*\{[^}]*overflow-x:\s*visible\s*;/s,
);
expect(mobileTaskDetailCss).not.toMatch(
/\.task-summary-token-table(?:\s|,|\{|[^{}]*\{[^}]*)display:\s*block\s*;/s,
);
expect(mobileTaskDetailCss).not.toMatch(
/\.task-summary-token-table\s+(?:thead|tbody|tfoot|tr|th|td)(?:\s|,|\{|[^{}]*\{[^}]*)display:\s*block\s*;/s,
);
expect(mobileTaskDetailCss).not.toMatch(
/\.task-summary-token-table\s+thead\s*\{[^}]*display:\s*none\s*;/s,
);
expect(mobileTaskDetailCss).not.toContain(".task-summary-token-table td::before");
});
it("keeps the Command Center per-model table wrapper scrollable", () => {
const commandCenterTableWrapRule = ruleBlock(commandCenterAreasCss, ".cc-table-wrap");
expect(commandCenterTableWrapRule).toContain("overflow-x: auto;");
});
});