diff --git a/.changeset/fn-8147-quick-add-shadcn-height.md b/.changeset/fn-8147-quick-add-shadcn-height.md new file mode 100644 index 0000000000..fddbba51bc --- /dev/null +++ b/.changeset/fn-8147-quick-add-shadcn-height.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Quick Add action buttons are no longer shrunk in shadcn themes. +category: fix +dev: Pins the .quick-entry-actions control height to literal :root tokens (28px desktop / 36px mobile) so shadcn's tighter --space-xl/--space-2xl scale no longer shrinks the composer (desktop + mobile). diff --git a/packages/dashboard/app/__tests__/quick-entry-action-row-height-parity.test.tsx b/packages/dashboard/app/__tests__/quick-entry-action-row-height-parity.test.tsx index 23f4e85554..4f6b5ee333 100644 --- a/packages/dashboard/app/__tests__/quick-entry-action-row-height-parity.test.tsx +++ b/packages/dashboard/app/__tests__/quick-entry-action-row-height-parity.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { loadAllAppCss, loadAllAppCssBaseOnly, loadStylesCss } from "../test/cssFixture"; +import { loadAllAppCss, loadAllAppCssBaseOnly, loadStylesCss, loadThemeDataCss } from "../test/cssFixture"; import { render, screen, act } from "@testing-library/react"; import { QuickEntryBox } from "../components/QuickEntryBox"; import type { Task } from "@fusion/core"; @@ -96,6 +96,8 @@ vi.mock("lucide-react", () => ({ Zap: () => null, Maximize2: () => null, Minimize2: () => null, + Eye: () => null, + EyeOff: () => null, })); vi.mock("../components/ModelSelectionModal", () => ({ @@ -173,9 +175,9 @@ describe("quick-entry action row height parity (FN-7680)", () => { /\.quick-entry-actions \.btn,\s*\n\s*\.quick-entry-actions \.wf-optional-steps-dropdown-trigger\s*\{[^}]*min-height:\s*([^;]+);/, ); expect(match).not.toBeNull(); - // Not the mobile touch-target literal (calc(var(--space-2xl) + var(--space-xs))); - // this must be a distinct desktop-width value declared outside any @media block. - expect(match![1].trim()).toBe("calc(var(--space-xl) + var(--space-xs))"); + // Not the mobile pinned token; this must be a distinct desktop-width value + // declared outside any @media block. + expect(match![1].trim()).toBe("var(--quick-entry-action-row-height-desktop)"); }); it("keeps the existing ≤768px touch-target min-height block applying to all .quick-entry-actions .btn (including Save)", () => { @@ -193,7 +195,51 @@ describe("quick-entry action row height parity (FN-7680)", () => { /\.quick-entry-actions \.btn,\s*\n\s*\.quick-entry-actions \.wf-optional-steps-dropdown-trigger\s*\{[^}]*min-height:\s*([^;]+);/, ); expect(mobileBlockMatch).not.toBeNull(); - expect(mobileBlockMatch![1].trim()).toBe("calc(var(--space-2xl) + var(--space-xs))"); + expect(mobileBlockMatch![1].trim()).toBe("var(--quick-entry-action-row-height-mobile)"); + }); + + it("pins desktop and mobile action-row heights outside the shadcn spacing scale", () => { + const baseOnlyCss = loadAllAppCssBaseOnly(); + const stylesCss = loadStylesCss(); + const themeDataCss = loadThemeDataCss(); + + const desktopRule = baseOnlyCss.match( + /\.quick-entry-actions \.btn,\s*\n\s*\.quick-entry-actions \.wf-optional-steps-dropdown-trigger\s*\{[^}]*min-height:\s*([^;]+);/, + ); + expect(desktopRule?.[1].trim()).toBe("var(--quick-entry-action-row-height-desktop)"); + + const allCss = loadAllAppCss(); + const mobileSectionStart = allCss.indexOf("Quick Entry Mobile Touch + Overflow Fixes"); + const mobileSection = allCss.slice(mobileSectionStart, mobileSectionStart + 1600); + const mobileRule = mobileSection.match( + /\.quick-entry-actions \.btn,\s*\n\s*\.quick-entry-actions \.wf-optional-steps-dropdown-trigger\s*\{[^}]*min-height:\s*([^;]+);/, + ); + expect(mobileRule?.[1].trim()).toBe("var(--quick-entry-action-row-height-mobile)"); + + const rootBlock = stylesCss.match(/:root\s*\{([\s\S]*?)\n\}/); + expect(rootBlock).not.toBeNull(); + for (const [token, literal] of [ + ["--quick-entry-action-row-height-desktop", "28px"], + ["--quick-entry-action-row-height-mobile", "36px"], + ]) { + const declaration = rootBlock![1].match(new RegExp(`${token}:\\s*([^;]+);`)); + expect(declaration?.[1].trim()).toBe(literal); + expect(declaration?.[1]).not.toContain("calc("); + expect(declaration?.[1]).not.toContain("var(--space"); + expect(themeDataCss).not.toContain(token); + } + + // Whole-file token scan covers each base selector and its light companion + // rules, preventing a variant-specific override from reintroducing drift. + for (const theme of [ + "shadcn", "shadcn-ember", "shadcn-custom", "shadcn-blue", "shadcn-green", + "shadcn-red", "shadcn-purple", "shadcn-pink", "shadcn-orange", "shadcn-yellow", + "shadcn-mono-red", "shadcn-mono-blue", "shadcn-mono-green", "shadcn-mono-purple", + "shadcn-mono-pink", "shadcn-mono-orange", "shadcn-mono-yellow", "shadcn-black", + "shadcn-gray", "shadcn-gray-blue", + ]) { + expect(themeDataCss).toContain(`[data-color-theme="${theme}"]`); + } }); it("does not modify the shared global .btn, .btn-sm, .btn-icon, .btn-task-create, or .dep-trigger rules in styles.css", () => { diff --git a/packages/dashboard/app/__tests__/quick-entry-workflow-trigger-height.test.tsx b/packages/dashboard/app/__tests__/quick-entry-workflow-trigger-height.test.tsx index 80f229130c..f0fdf2323e 100644 --- a/packages/dashboard/app/__tests__/quick-entry-workflow-trigger-height.test.tsx +++ b/packages/dashboard/app/__tests__/quick-entry-workflow-trigger-height.test.tsx @@ -89,6 +89,8 @@ vi.mock("lucide-react", () => ({ Zap: () => null, Maximize2: () => null, Minimize2: () => null, + Eye: () => null, + EyeOff: () => null, })); vi.mock("../components/ModelSelectionModal", () => ({ @@ -351,7 +353,7 @@ describe("quick-entry-actions fixed-height parity, not just a min-height floor ( // Mobile and desktop intentionally use different fixed heights (36px vs // 28px touch targets) — that is expected; parity is required WITHIN each // breakpoint, not across breakpoints. - expect(minHeightMatch![1].trim()).toBe("calc(var(--space-2xl) + var(--space-xs))"); + expect(minHeightMatch![1].trim()).toBe("var(--quick-entry-action-row-height-mobile)"); }); it("does not modify the shared global .btn-sm / .btn-icon / .dep-trigger rules in styles.css (cross-surface regression guard)", () => { @@ -408,7 +410,7 @@ describe("quick-entry-actions fixed-height parity, not just a min-height floor ( // The desktop base (non-media) rule must NOT contain this Save-specific // override — desktop/tablet Save sizing is intentionally left untouched. const withoutMediaBlocks = cssContent.replace(/@media[^{]*\{(?:[^{}]*\{[^{}]*\})*[^{}]*\}/g, ""); - expect(withoutMediaBlocks).not.toMatch(/\[data-testid="quick-entry-save"\]/); + expect(withoutMediaBlocks).not.toMatch(/\.quick-entry-actions \[data-testid="quick-entry-save"\]\s*\{/); // The shared `.quick-entry-actions .btn, .quick-entry-actions // .wf-optional-steps-dropdown-trigger` fixed-height contract (governing diff --git a/packages/dashboard/app/components/QuickEntryBox.css b/packages/dashboard/app/components/QuickEntryBox.css index 1baaab2b31..99393b9f7f 100644 --- a/packages/dashboard/app/components/QuickEntryBox.css +++ b/packages/dashboard/app/components/QuickEntryBox.css @@ -305,11 +305,15 @@ while guaranteeing IDENTICAL resolved height across Save, workflow trigger, Attach, GitHub toggle, Fast, Priority, Deps, Subtask, and the optional-steps trigger — not merely all-at-least-28px. Applies at both this desktop rule and the mobile touch-target block below (FN-5751: never a breakpoint-only fix). + +FNXC:QuickAddActionRow 2026-07-16-14:00: +FN-8147 replaces the spacing-derived fixed height with a pinned literal root +token so every shadcn theme retains the default 28px desktop control height. */ .quick-entry-actions .btn, .quick-entry-actions .wf-optional-steps-dropdown-trigger { - min-height: calc(var(--space-xl) + var(--space-xs)); - max-height: calc(var(--space-xl) + var(--space-xs)); + min-height: var(--quick-entry-action-row-height-desktop); + max-height: var(--quick-entry-action-row-height-desktop); line-height: var(--line-height-tight); align-items: center; overflow: hidden; @@ -385,7 +389,7 @@ FNXC:QuickAddWorkflow 2026-07-08-00:00: FN-7677 — the workflow trigger shares `.btn.btn-sm.dep-trigger` classes with other surfaces (InlineCreateCard, NewTaskModal, TaskDetailModal, TaskForm), but the shared `.dep-trigger, .inline-create-model-trigger` rule in styles.css sets `padding: 3px 8px`, overriding `.btn-sm`'s `padding: 4px 10px` and making this trigger ~2px shorter than its Save/Fast/Subtask `.btn.btn-sm` siblings in `.quick-entry-actions`. Re-assert `.btn-sm`'s own padding value scoped to this selector only (do not touch the shared global `.dep-trigger` rule — other surfaces still depend on its 3px/8px sizing) so the quick-add action row reads as one uniform height. FNXC:QuickAddWorkflow 2026-07-08-09:30: -FN-7682 — the tokenized-CSS test forbids raw px in the workflow-selector rules, so the `4px 10px` literal is replaced with `var(--space-sm) var(--space-md)` (vertical then horizontal). Height parity is unaffected: box height for this trigger is governed by the FN-7680 `.quick-entry-actions .btn, .quick-entry-actions .wf-optional-steps-dropdown-trigger { min-height: calc(var(--space-xl) + var(--space-xs)); }` normalization (desktop and the matching mobile block), not by this element's own padding — the padding only controls internal content spacing and stays at or below that min-height. +FN-7682 — the tokenized-CSS test forbids raw px in the workflow-selector rules, so the `4px 10px` literal is replaced with `var(--space-sm) var(--space-md)` (vertical then horizontal). Height parity is unaffected: box height for this trigger is governed by the FN-7680/FN-8147 `.quick-entry-actions .btn, .quick-entry-actions .wf-optional-steps-dropdown-trigger { min-height: var(--quick-entry-action-row-height-desktop); }` normalization (desktop and the matching mobile block), not by this element's own padding — the padding only controls internal content spacing and stays at or below that min-height. */ .quick-entry-workflow-trigger { display: inline-flex; @@ -767,9 +771,9 @@ FN-7682 — the tokenized-CSS test forbids raw px in the workflow-selector rules .quick-entry-actions .btn, .quick-entry-actions .wf-optional-steps-dropdown-trigger { /* FNXC:WorkflowOptionalSteps 2026-06-25-00:00: The optional-steps trigger is not a `.btn`, so opt it into the same mobile quick-entry touch-target contract as the surrounding action buttons without adding a wrapper that could leave an empty shell for zero-step workflows. */ - /* FNXC:QuickAddActionRow 2026-07-08-10:15: FN-7683 — mirror the desktop upgrade: a bare min-height floor cannot equalize a variant (e.g. Save's text+icon content) that naturally resolves taller than the touch-target height. Pair min-height with an equal max-height plus centered, tokenized line-height so every control lands on the SAME resolved touch-target height at this breakpoint, not merely >= it. */ - min-height: calc(var(--space-2xl) + var(--space-xs)); - max-height: calc(var(--space-2xl) + var(--space-xs)); + /* FNXC:QuickAddActionRow 2026-07-16-14:00: FN-7683's fixed-height mobile contract remains intact; FN-8147 uses the pinned literal root token so shadcn's tighter spacing cannot shrink its 36px touch target. */ + min-height: var(--quick-entry-action-row-height-mobile); + max-height: var(--quick-entry-action-row-height-mobile); line-height: var(--line-height-tight); align-items: center; overflow: hidden; diff --git a/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx b/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx index f8c30d00f1..27f7b04c2a 100644 --- a/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx +++ b/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx @@ -3538,7 +3538,7 @@ describe("QuickEntryBox", () => { QUICK_ENTRY_BOX_CSS, ".quick-entry-actions .btn,\n .quick-entry-actions .wf-optional-steps-dropdown-trigger", ); - expect(touchRule).toContain("min-height: calc(var(--space-2xl) + var(--space-xs))"); + expect(touchRule).toContain("min-height: var(--quick-entry-action-row-height-mobile)"); }); it("shows and clears a Quick Add drop target only for file drags", () => { @@ -4514,7 +4514,7 @@ describe("QuickEntryBox", () => { ); expect(optionalTriggerRule).not.toBeNull(); - expect(cssDeclarationValue(optionalTriggerRule!, "min-height")).toBe("calc(var(--space-2xl) + var(--space-xs))"); + expect(cssDeclarationValue(optionalTriggerRule!, "min-height")).toBe("var(--quick-entry-action-row-height-mobile)"); }); it("keeps inline deps/models controls in touch-target button classes on mobile", () => { diff --git a/packages/dashboard/app/styles.css b/packages/dashboard/app/styles.css index cec0678527..45dd160e0a 100644 --- a/packages/dashboard/app/styles.css +++ b/packages/dashboard/app/styles.css @@ -146,6 +146,16 @@ html { --space-xl: 24px; --space-2xl: 32px; + /* + FNXC:QuickAddActionRow 2026-07-16-14:00: + FN-8147 pins Quick Add action-row control heights to literal values rather than + deriving them from --space-* so shadcn's tighter spacing scale cannot shrink + composer controls. These match the default resolved heights: 28px desktop and + 36px mobile. + */ + --quick-entry-action-row-height-desktop: 28px; + --quick-entry-action-row-height-mobile: 36px; + /* Border Radius Scale */ --radius-sm: 4px; --radius-md: 8px;