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:
gsxdsm
2026-07-16 15:33:47 -07:00
parent f57dfc03b6
commit 4d73232a70
6 changed files with 84 additions and 15 deletions

View 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).

View File

@@ -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", () => {

View File

@@ -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

View File

@@ -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;

View File

@@ -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", () => {

View File

@@ -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;