From 0856d3d3fdb3d23a2ea5dd292977484dd98d09d7 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 13 Jun 2026 11:03:30 -0700 Subject: [PATCH] FN-6380: widen Activity Log tablet layout Widen the Activity Log modal at tablet widths so header controls stay reachable. - Add component-scoped tablet CSS that widens only the Activity Log modal. - Allow the Activity Log header and action controls to wrap while keeping close pinned right. - Cover the tablet-only layout contract with a CSS regression test. Files changed: .../__tests__/activity-log-tablet-layout.test.ts | 73 ++++++++++++++++++++++ packages/dashboard/app/components/ScriptsModal.css | 49 +++++++++++++++ 2 files changed, 122 insertions(+) Fusion-Task-Id: FN-6380 Fusion-Task-Lineage: ae7cf5a6-c69d-4756-a4fa-1e4346b9c077 --- .../activity-log-tablet-layout.test.ts | 73 +++++++++++++++++++ .../dashboard/app/components/ScriptsModal.css | 49 +++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 packages/dashboard/app/__tests__/activity-log-tablet-layout.test.ts diff --git a/packages/dashboard/app/__tests__/activity-log-tablet-layout.test.ts b/packages/dashboard/app/__tests__/activity-log-tablet-layout.test.ts new file mode 100644 index 0000000000..4452309089 --- /dev/null +++ b/packages/dashboard/app/__tests__/activity-log-tablet-layout.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from "vitest"; +import { loadAllAppCss } from "../test/cssFixture"; + +/** + * Stylesheet regression test for Activity Log tablet layout. + * + * The desktop .modal-lg width is too narrow for the Activity Log header at + * tablet widths, so a component-scoped tablet media block must widen only the + * Activity Log modal and wrap the header controls. If these tablet rules are + * removed, refresh/close can be clipped between 769px and 1024px. + */ +describe("activity-log-tablet-layout.css", () => { + const cssContent = loadAllAppCss(); + + function extractTabletMediaBlocks(content: string): string { + const blocks: string[] = []; + const regex = /@media[^{}]*\(min-width:\s*769px\)[^{}]*\(max-width:\s*1024px\)[^{}]*\{/g; + let match: RegExpExecArray | null; + + while ((match = regex.exec(content)) !== null) { + const startIdx = match.index + match[0].length; + let braceCount = 1; + let endIdx = startIdx; + + while (braceCount > 0 && endIdx < content.length) { + if (content[endIdx] === "{") braceCount++; + if (content[endIdx] === "}") braceCount--; + endIdx++; + } + + if (braceCount === 0) { + blocks.push(content.slice(startIdx, endIdx - 1)); + } + } + + return blocks.join("\n"); + } + + const tabletCss = extractTabletMediaBlocks(cssContent); + + it("defines tablet Activity Log rules for the broken 769px–1024px range", () => { + expect(tabletCss).toContain(".activity-log-modal"); + expect(tabletCss).toContain(".activity-log-header"); + }); + + it("widens only the Activity Log modal beyond the modal-lg base width", () => { + expect(tabletCss).toMatch(/\.activity-log-modal\s*\{[^}]*width:\s*calc\(100vw\s*-\s*var\(--space-2xl\)\)/); + expect(tabletCss).toMatch(/\.activity-log-modal\s*\{[^}]*max-width:\s*calc\(100vw\s*-\s*var\(--space-2xl\)\)/); + }); + + it("does not redefine the global modal-lg width inside the tablet block", () => { + expect(tabletCss).not.toMatch(/\.modal-lg\s*\{/); + }); + + it("allows the Activity Log header to wrap on tablet", () => { + expect(tabletCss).toMatch(/\.activity-log-header\s*\{[^}]*flex-wrap:\s*wrap/); + }); + + it("moves actions to a reachable wrapping row on tablet", () => { + expect(tabletCss).toMatch(/\.activity-log-actions\s*\{[^}]*flex:\s*1\s+1\s+100%/); + expect(tabletCss).toMatch(/\.activity-log-actions\s*\{[^}]*flex-wrap:\s*wrap/); + }); + + it("keeps the close button pinned to the top-right row on tablet", () => { + expect(tabletCss).toMatch(/\.activity-log-header\s+\.modal-close\s*\{[^}]*order:\s*\d/); + expect(tabletCss).toMatch(/\.activity-log-header\s+\.modal-close\s*\{[^}]*margin-left:\s*auto/); + }); + + it("keeps filters and refresh/clear controls reachable when optional controls render", () => { + expect(tabletCss).toMatch(/\.activity-log-filter,\s*\n\s*\.activity-log-filter--project\s*\{[^}]*flex:\s*1\s+1\s+0/); + expect(tabletCss).toMatch(/\.activity-log-refresh,\s*\n\s*\.activity-log-clear\s*\{[^}]*flex-shrink:\s*0/); + }); +}); diff --git a/packages/dashboard/app/components/ScriptsModal.css b/packages/dashboard/app/components/ScriptsModal.css index 33be4d3ecd..332ad44312 100644 --- a/packages/dashboard/app/components/ScriptsModal.css +++ b/packages/dashboard/app/components/ScriptsModal.css @@ -1595,6 +1595,55 @@ border-color: var(--ws-error-dark); } +/* ── Activity Log — Tablet (769px–1024px) ────────────────────────── */ + +@media (min-width: 769px) and (max-width: 1024px) { + /* Widen only the Activity Log modal; keep the global .modal-lg width unchanged. */ + .activity-log-modal { + width: calc(100vw - var(--space-2xl)); + max-width: calc(100vw - var(--space-2xl)); + } + + /* Header: title on left, close on right of top row, actions wrap below. */ + .activity-log-header { + flex-wrap: wrap; + gap: var(--space-sm); + } + + .activity-log-title { + flex: 1 1 auto; + order: 0; + } + + .activity-log-actions { + flex: 1 1 100%; + flex-wrap: wrap; + gap: var(--space-sm); + order: 2; + } + + .activity-log-header .modal-close { + order: 1; + margin-left: auto; + flex: 0 0 auto; + } + + .activity-log-filter, + .activity-log-filter--project { + flex: 1 1 0; + min-width: 0; + } + + .activity-log-filter-select { + width: 100%; + } + + .activity-log-refresh, + .activity-log-clear { + flex-shrink: 0; + } +} + /* ── Activity Log — Mobile (≤ 768px) ─────────────────────────────── */ @media (max-width: 768px) {