FN-8147: pin quick-add action heights across shadcn themes
Keep Quick Add action controls at their intended desktop and mobile sizes in every shadcn theme. - Define literal root tokens for the 28px desktop and 36px mobile action-row heights. - Apply the pinned tokens to Quick Add buttons and workflow triggers. - Expand CSS parity coverage and add a patch changeset. Files changed: .changeset/fn-8147-quick-add-shadcn-height.md | 7 +++ .../quick-entry-action-row-height-parity.test.tsx | 56 ++++++++++++++++++++-- .../quick-entry-workflow-trigger-height.test.tsx | 6 ++- .../dashboard/app/components/QuickEntryBox.css | 16 ++++--- .../components/__tests__/QuickEntryBox.test.tsx | 4 +- packages/dashboard/app/styles.css | 10 ++++ 6 files changed, 84 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-8147 Fusion-Task-Lineage: 99d51d75-2fce-4d35-963c-4370c3f36add Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8147-quick-add-shadcn-height.md
Normal file
7
.changeset/fn-8147-quick-add-shadcn-height.md
Normal file
@@ -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).
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user