diff --git a/.changeset/fn-8396-task-detail-tablet-icon-size.md b/.changeset/fn-8396-task-detail-tablet-icon-size.md new file mode 100644 index 0000000000..a4e1e95528 --- /dev/null +++ b/.changeset/fn-8396-task-detail-tablet-icon-size.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Task detail inline action icons now render at a consistent size on tablet screens. +category: fix +dev: Scoped the .detail-meta-inline-controls SVG sizing so every inline-row icon (including nested ProviderIcon SVGs) resolves to the shared --icon-size-sm token at desktop, tablet (769–1024px), and mobile. No handler/behavior changes; preserves the icon-only row, square-box sizing, size-prop-less oversight Eye/EyeOff, and the mobile wrap fallback. 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 dbc6340f83..0133dbdcc6 100644 --- a/packages/dashboard/app/__tests__/browser-layout-smoke-fixture.test.ts +++ b/packages/dashboard/app/__tests__/browser-layout-smoke-fixture.test.ts @@ -12,6 +12,24 @@ describe("browser layout smoke fixture", () => { expect(html).toContain("pr-checks__details-link"); }); + it("includes Task Detail inline icon fixtures for all optional-control variants", () => { + const html = createSmokeHtml(); + expect(html).toContain('data-smoke="task-detail-inline-row-fixtures"'); + for (const variant of ["full", "without-github", "without-oversight", "without-optionals"]) { + expect(html).toContain(`data-smoke="task-detail-inline-row-${variant}"`); + } + for (const testId of [ + "detail-inline-attach", + "detail-inline-github-toggle", + "detail-oversight-menu-trigger", + "detail-priority-trigger", + "detail-execution-mode-toggle", + ]) { + expect(html).toContain(`data-testid="${testId}"`); + } + expect(html).toContain(' { const html = createSmokeHtml(); expect(html).toContain('data-smoke="quick-add-save-fixtures"'); diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index d62b04102a..3e77291057 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -414,6 +414,20 @@ space or icon tokens from making this cluster visually uneven. border-radius: var(--detail-control-border-radius); } +/* +FNXC:TaskDetailModalResponsive 2026-07-19-12:00: +FN-8396 requires every inline action icon to resolve to `--icon-size-sm` at +all desktop, tablet, and mobile widths. The global `.btn-icon > svg` rule +cannot reach ProviderIcon's wrapped SVG, so keep this descendant rule scoped +to Task Detail instead of changing global button behavior or adding an +explicit size prop to the Oversight Eye/EyeOff icons. +*/ +.detail-meta-inline-controls svg { + width: var(--icon-size-sm); + height: var(--icon-size-sm); + flex-shrink: 0; +} + /* FNXC:QuickAddActionRow 2026-07-17-12:00: FN-8209: Task Detail uses the same compact icon-only priority affordance as diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx index 5af7c10c51..ebe57c3f66 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx @@ -9,6 +9,8 @@ regex. The now-universal Oversight overflow trigger's aria-label is "Oversight actions", which also matches `/actions/i` and made every such query ambiguous once the trigger stopped being a mobile-only affordance. */ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, fireEvent, act, waitFor } from "@testing-library/react"; @@ -1411,6 +1413,29 @@ describe("TaskDetailModal", () => { expect(fast).not.toHaveTextContent("Fast"); }); + it("keeps every inline action icon-only with the production size-prop contracts", () => { + const source = readFileSync(resolve(__dirname, "../TaskDetailModal.tsx"), "utf8"); + const rowStart = source.indexOf('data-testid="detail-meta-inline-controls"'); + const rowEnd = source.indexOf('className="detail-hidden-file-input"', rowStart); + const row = source.slice(rowStart, rowEnd); + + // FNXC:TaskDetailModalResponsive 2026-07-19-12:00: The row stays ordered + // attach → GitHub → Oversight → priority → Fast; CSS owns icon parity. + expect(row).toMatch(/]*aria-hidden="true"/); + expect(row).toMatch(/]*aria-hidden="true"/); + expect(row).toMatch(/]*aria-hidden="true"/); + expect(row).toMatch(/overseerTriggerOn \? : /); + expect(row).not.toMatch(/<(?:Eye|EyeOff)\s+[^>]*\bsize=/); + expect(row.indexOf("detail-inline-attach")).toBeLessThan(row.indexOf("detail-inline-github-toggle")); + expect(row.indexOf("detail-inline-github-toggle")).toBeLessThan(row.indexOf("detail-oversight-menu-trigger")); + expect(row.indexOf("detail-oversight-menu-trigger")).toBeLessThan(row.indexOf("detail-priority-trigger")); + expect(row.indexOf("detail-priority-trigger")).toBeLessThan(row.indexOf("detail-execution-mode-toggle")); + for (const label of ["aria-label", "title"]) { + expect(row.match(new RegExp(label, "g"))?.length).toBeGreaterThanOrEqual(5); + } + }); + it("removes bespoke toolbar SVG sizing rules", () => { const css = readDashboardStylesSource(); expect(css).not.toMatch(/\.detail-oversight-menu-trigger svg\s*\{[^}]*width:\s*1em/); diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx index aa52f13567..16e71163a5 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx @@ -253,6 +253,22 @@ describe("TaskDetailModal", () => { expect(css).not.toMatch(/@media \(max-width: 640px\)\s*\{[^}]*\.detail-meta-inline-controls\s*\{[^}]*flex-direction:\s*column;/); }); + it("scopes tokenized SVG sizing to every inline-row descendant without changing breakpoint scaffolding", () => { + const css = readDashboardStylesSource(); + const tabletBlock = getCssAtRuleBlockContaining(css, "@media (min-width: 769px) and (max-width: 1024px)", ".modal.task-detail-modal"); + const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-meta-inline-controls"); + const rowSvgBlock = getExactCssRuleBlock(css, ".detail-meta-inline-controls svg"); + + // FNXC:TaskDetailModalResponsive 2026-07-19-12:00: Source guards preserve + // the scoped token contract; Blink smoke separately proves computed sizes. + expect(rowSvgBlock).toContain("width: var(--icon-size-sm);"); + expect(rowSvgBlock).toContain("height: var(--icon-size-sm);"); + expect(rowSvgBlock).toContain("flex-shrink: 0;"); + expect(css).not.toMatch(/\.detail-meta-inline-controls svg\s*\{[^}]*width:\s*1em/); + expect(tabletBlock).not.toBe(""); + expect(mobileBlock).toMatch(/\.detail-meta-inline-controls\s*\{[^}]*flex-wrap:\s*wrap;/); + }); + it("gives every task-detail inline action the shared square tokenized box across themes (FN-8287)", () => { const css = readDashboardStylesSource(); const sharedSizingSelector = [ diff --git a/packages/dashboard/scripts/browser-layout-smoke.mjs b/packages/dashboard/scripts/browser-layout-smoke.mjs index e2907f7654..301475507e 100644 --- a/packages/dashboard/scripts/browser-layout-smoke.mjs +++ b/packages/dashboard/scripts/browser-layout-smoke.mjs @@ -159,6 +159,29 @@ export function createSmokeHtml() { `)).join(""); + /* + FNXC:TaskDetailModalResponsive 2026-07-19-12:00: + FN-8396 mirrors Task Detail's direct and wrapped SVG structures so Blink can + prove the scoped row rule normalizes ProviderIcon alongside the CSS-only + Oversight Eye/EyeOff contract at every responsive breakpoint. + */ + const taskDetailInlineRowFixtures = [ + ["full", true, true], + ["without-github", false, true], + ["without-oversight", true, false], + ["without-optionals", false, false], + ].map(([variant, includeGithub, includeOversight]) => ` +
+
+ + ${includeGithub ? '' : ""} + ${includeOversight ? '' : ""} +
+ +
+ + `).join(""); + return ` @@ -238,6 +261,10 @@ export function createSmokeHtml() { ${quickAddComposerFixtures} +
+ ${taskDetailInlineRowFixtures} +
+