FN-8005: normalize Quick Add action icon sizes
Unify the Quick Add primary action cluster around the shared icon-only sizing treatment. - Apply btn-icon styling and 14px SVG sizing to GitHub, session advisor, priority, and Fast controls. - Cover primary cluster uniformity across mobile, toggle, and priority states. - Add a patch changeset for the Quick Add visual fix. Files changed: .changeset/fn-8005-quick-add-icon-size-parity.md | 7 +++ .../dashboard/app/components/QuickEntryBox.tsx | 19 ++++--- .../components/__tests__/QuickEntryBox.test.tsx | 63 ++++++++++++++++++++-- 3 files changed, 79 insertions(+), 10 deletions(-) Fusion-Task-Id: FN-8005 Fusion-Task-Lineage: c912fb90-1e49-4c4d-8664-4caf1489f890 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8005-quick-add-icon-size-parity.md
Normal file
7
.changeset/fn-8005-quick-add-icon-size-parity.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Quick Add overseer, priority, fast, GitHub, and attach icons now render at one uniform size.
|
||||||
|
category: fix
|
||||||
|
dev: QuickEntryBox primary-group icon-only buttons (Eye/EyeOff, PriorityIcon, Zap, GitHub ProviderIcon, Paperclip) all use the icon-only btn-icon treatment (--icon-size-sm) for a uniform 14px cluster; ProviderIcon sizeMap unchanged.
|
||||||
@@ -2181,6 +2181,11 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
FNXC:QuickAddAttachments 2026-06-30-00:00 (relocated 2026-07-10): the attachment affordance
|
FNXC:QuickAddAttachments 2026-06-30-00:00 (relocated 2026-07-10): the attachment affordance
|
||||||
stays adjacent to Save (now immediately to its LEFT) preserving the icon-only label, hidden
|
stays adjacent to Save (now immediately to its LEFT) preserving the icon-only label, hidden
|
||||||
file input trigger, and pending-count badge.
|
file input trigger, and pending-count badge.
|
||||||
|
|
||||||
|
FNXC:QuickAddActionRow 2026-07-15-00:00:
|
||||||
|
Attach, GitHub, session advisor, Priority, and Fast are one icon-only cluster. Every control
|
||||||
|
uses `btn-icon` so its SVG resolves to the shared `--icon-size-sm` token; do not fork
|
||||||
|
ProviderIcon's shared size map to size this one GitHub use case.
|
||||||
*/}
|
*/}
|
||||||
<div className="quick-entry-primary-group" data-testid="quick-entry-primary-group">
|
<div className="quick-entry-primary-group" data-testid="quick-entry-primary-group">
|
||||||
<button
|
<button
|
||||||
@@ -2200,7 +2205,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm ${effectiveGithubTracking ? "btn-primary" : ""}`}
|
className={`btn btn-icon btn-sm ${effectiveGithubTracking ? "btn-primary" : ""}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setGithubTrackingOverride((prev) => !(prev ?? projectGithubTrackingDefault));
|
setGithubTrackingOverride((prev) => !(prev ?? projectGithubTrackingDefault));
|
||||||
}}
|
}}
|
||||||
@@ -2225,7 +2230,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
*/}
|
*/}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm ${effectiveSessionAdvisor ? "btn-primary" : ""}`}
|
className={`btn btn-icon btn-sm ${effectiveSessionAdvisor ? "btn-primary" : ""}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSessionAdvisorOverride((prev) => {
|
setSessionAdvisorOverride((prev) => {
|
||||||
const currentEffective = prev ?? projectSessionAdvisorDefault;
|
const currentEffective = prev ?? projectSessionAdvisorDefault;
|
||||||
@@ -2239,14 +2244,14 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
title={sessionAdvisorToggleLabel}
|
title={sessionAdvisorToggleLabel}
|
||||||
aria-label={sessionAdvisorToggleLabel}
|
aria-label={sessionAdvisorToggleLabel}
|
||||||
>
|
>
|
||||||
{effectiveSessionAdvisor ? <Eye size={12} aria-hidden="true" /> : <EyeOff size={12} aria-hidden="true" />}
|
{effectiveSessionAdvisor ? <Eye size={14} aria-hidden="true" /> : <EyeOff size={14} aria-hidden="true" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="priority-trigger-wrap" ref={priorityPickerRef}>
|
<div className="priority-trigger-wrap" ref={priorityPickerRef}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
className="btn btn-sm dep-trigger"
|
className="btn btn-icon btn-sm dep-trigger"
|
||||||
data-testid="quick-entry-priority-button"
|
data-testid="quick-entry-priority-button"
|
||||||
title={priorityButtonLabel}
|
title={priorityButtonLabel}
|
||||||
aria-label={priorityButtonLabel}
|
aria-label={priorityButtonLabel}
|
||||||
@@ -2271,7 +2276,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* FNXC:PriorityColorCoding 2026-07-11-00:00: The quick-add icon-only priority trigger must preview urgency color from priorityIndicator without changing its label, test id, or picker behavior. */}
|
{/* FNXC:PriorityColorCoding 2026-07-11-00:00: The quick-add icon-only priority trigger must preview urgency color from priorityIndicator without changing its label, test id, or picker behavior. */}
|
||||||
<PriorityIcon size={12} aria-hidden="true" style={{ color: getPriorityColorVar(priority) }} />
|
<PriorityIcon size={14} aria-hidden="true" style={{ color: getPriorityColorVar(priority) }} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2316,7 +2321,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm ${isFastMode ? "btn-primary" : ""}`}
|
className={`btn btn-icon btn-sm ${isFastMode ? "btn-primary" : ""}`}
|
||||||
onClick={toggleFastMode}
|
onClick={toggleFastMode}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
aria-pressed={isFastMode}
|
aria-pressed={isFastMode}
|
||||||
@@ -2324,7 +2329,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
title={fastToggleLabel}
|
title={fastToggleLabel}
|
||||||
aria-label={fastToggleLabel}
|
aria-label={fastToggleLabel}
|
||||||
>
|
>
|
||||||
<Zap size={12} aria-hidden="true" />
|
<Zap size={14} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ function mockMobileViewport() {
|
|||||||
FNXC:BoardComposer 2026-07-10-12:00:
|
FNXC:BoardComposer 2026-07-10-12:00:
|
||||||
DOM order mirrors the reorganized composer action row: the options group (subtask, deps,
|
DOM order mirrors the reorganized composer action row: the options group (subtask, deps,
|
||||||
models, node, agent) comes first, followed by the right-aligned primary group (attach, GitHub,
|
models, node, agent) comes first, followed by the right-aligned primary group (attach, GitHub,
|
||||||
Priority, Fast, Save) with Save as the LAST control.
|
session advisor, Priority, Fast, Save) with Save as the LAST control.
|
||||||
*/
|
*/
|
||||||
const QUICK_ENTRY_ACTION_BUTTONS = [
|
const QUICK_ENTRY_ACTION_BUTTONS = [
|
||||||
["Subtask", "subtask-button"],
|
["Subtask", "subtask-button"],
|
||||||
@@ -426,11 +426,20 @@ const QUICK_ENTRY_ACTION_BUTTONS = [
|
|||||||
["Agent", "quick-entry-agent-button"],
|
["Agent", "quick-entry-agent-button"],
|
||||||
["Attach", "quick-entry-attach"],
|
["Attach", "quick-entry-attach"],
|
||||||
["GitHub", "quick-entry-github-toggle"],
|
["GitHub", "quick-entry-github-toggle"],
|
||||||
|
["Session advisor", "quick-entry-session-advisor-toggle"],
|
||||||
["Priority", "quick-entry-priority-button"],
|
["Priority", "quick-entry-priority-button"],
|
||||||
["Fast", "quick-entry-fast-toggle"],
|
["Fast", "quick-entry-fast-toggle"],
|
||||||
["Save", "quick-entry-save"],
|
["Save", "quick-entry-save"],
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const QUICK_ENTRY_PRIMARY_ICON_BUTTON_IDS = [
|
||||||
|
"quick-entry-attach",
|
||||||
|
"quick-entry-github-toggle",
|
||||||
|
"quick-entry-session-advisor-toggle",
|
||||||
|
"quick-entry-priority-button",
|
||||||
|
"quick-entry-fast-toggle",
|
||||||
|
] as const;
|
||||||
|
|
||||||
const QUICK_ENTRY_PRIORITY_ICON_CLASS: Record<TaskPriority, string> = {
|
const QUICK_ENTRY_PRIORITY_ICON_CLASS: Record<TaskPriority, string> = {
|
||||||
low: "lucide-arrow-down",
|
low: "lucide-arrow-down",
|
||||||
normal: "lucide-flag",
|
normal: "lucide-flag",
|
||||||
@@ -456,6 +465,20 @@ function expectPriorityOptionColor(priority: TaskPriority) {
|
|||||||
expect(icon?.getAttribute("style")).toContain(`color: ${getPriorityColorVar(priority)}`);
|
expect(icon?.getAttribute("style")).toContain(`color: ${getPriorityColorVar(priority)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FNXC:QuickAddActionRow 2026-07-15-00:00:
|
||||||
|
* The primary action cluster is visually uniform only when every icon control opts into btn-icon.
|
||||||
|
* Assert the shared treatment rather than SVG attributes because ProviderIcon's public sm size is
|
||||||
|
* intentionally broader than this local CSS override.
|
||||||
|
*/
|
||||||
|
function expectQuickEntryPrimaryIconCluster() {
|
||||||
|
for (const testId of QUICK_ENTRY_PRIMARY_ICON_BUTTON_IDS) {
|
||||||
|
const button = screen.getByTestId(testId);
|
||||||
|
expect(button).toHaveClass("btn-icon");
|
||||||
|
expect(button.querySelector("svg")).not.toBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("QuickEntryBox", () => {
|
describe("QuickEntryBox", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -894,16 +917,17 @@ describe("QuickEntryBox", () => {
|
|||||||
expandQuickEntry();
|
expandQuickEntry();
|
||||||
|
|
||||||
const actionButtonTestIds = getActionButtonTestIdsInDomOrder();
|
const actionButtonTestIds = getActionButtonTestIdsInDomOrder();
|
||||||
expect(actionButtonTestIds.slice(-5)).toEqual([
|
expect(actionButtonTestIds.slice(-6)).toEqual([
|
||||||
"quick-entry-attach",
|
"quick-entry-attach",
|
||||||
"quick-entry-github-toggle",
|
"quick-entry-github-toggle",
|
||||||
|
"quick-entry-session-advisor-toggle",
|
||||||
"quick-entry-priority-button",
|
"quick-entry-priority-button",
|
||||||
"quick-entry-fast-toggle",
|
"quick-entry-fast-toggle",
|
||||||
"quick-entry-save",
|
"quick-entry-save",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const primaryGroup = screen.getByTestId("quick-entry-primary-group");
|
const primaryGroup = screen.getByTestId("quick-entry-primary-group");
|
||||||
for (const testId of ["quick-entry-attach", "quick-entry-github-toggle", "quick-entry-priority-button", "quick-entry-fast-toggle", "quick-entry-save"]) {
|
for (const testId of ["quick-entry-attach", "quick-entry-github-toggle", "quick-entry-session-advisor-toggle", "quick-entry-priority-button", "quick-entry-fast-toggle", "quick-entry-save"]) {
|
||||||
expect(primaryGroup.contains(screen.getByTestId(testId))).toBe(true);
|
expect(primaryGroup.contains(screen.getByTestId(testId))).toBe(true);
|
||||||
}
|
}
|
||||||
const optionsGroup = screen.getByTestId("quick-entry-options-group");
|
const optionsGroup = screen.getByTestId("quick-entry-options-group");
|
||||||
@@ -1004,6 +1028,18 @@ describe("QuickEntryBox", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("keeps the uniform icon cluster in the shared mobile button touch-target contract", async () => {
|
||||||
|
await renderMobileQuickEntryWithEnabledActions();
|
||||||
|
const actions = screen.getByTestId("quick-entry-actions");
|
||||||
|
|
||||||
|
expectQuickEntryPrimaryIconCluster();
|
||||||
|
for (const testId of QUICK_ENTRY_PRIMARY_ICON_BUTTON_IDS) {
|
||||||
|
const button = screen.getByTestId(testId);
|
||||||
|
expect(button).toHaveClass("btn");
|
||||||
|
expect(actions.contains(button)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("captures an SVG touch target inside the priority button and opens the picker", async () => {
|
it("captures an SVG touch target inside the priority button and opens the picker", async () => {
|
||||||
await renderMobileQuickEntryWithEnabledActions();
|
await renderMobileQuickEntryWithEnabledActions();
|
||||||
const priorityButton = screen.getByTestId("quick-entry-priority-button");
|
const priorityButton = screen.getByTestId("quick-entry-priority-button");
|
||||||
@@ -1241,6 +1277,9 @@ describe("QuickEntryBox", () => {
|
|||||||
case "quick-entry-github-toggle":
|
case "quick-entry-github-toggle":
|
||||||
expect(screen.getByTestId(testId)).toHaveAttribute("aria-pressed", "false");
|
expect(screen.getByTestId(testId)).toHaveAttribute("aria-pressed", "false");
|
||||||
break;
|
break;
|
||||||
|
case "quick-entry-session-advisor-toggle":
|
||||||
|
expect(screen.getByTestId(testId)).toHaveAttribute("aria-pressed", "true");
|
||||||
|
break;
|
||||||
case "quick-entry-priority-button":
|
case "quick-entry-priority-button":
|
||||||
expect(await screen.findByTestId("quick-entry-priority-option-normal")).toBeTruthy();
|
expect(await screen.findByTestId("quick-entry-priority-option-normal")).toBeTruthy();
|
||||||
break;
|
break;
|
||||||
@@ -2162,6 +2201,24 @@ describe("QuickEntryBox", () => {
|
|||||||
expectQuickEntryPriorityButton("normal");
|
expectQuickEntryPriorityButton("normal");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps every primary icon control in one btn-icon cluster across toggle and priority states", () => {
|
||||||
|
renderQuickEntryBox({});
|
||||||
|
expandQuickEntry();
|
||||||
|
|
||||||
|
expectQuickEntryPrimaryIconCluster();
|
||||||
|
fireEvent.click(screen.getByTestId("quick-entry-session-advisor-toggle"));
|
||||||
|
fireEvent.click(screen.getByTestId("quick-entry-github-toggle"));
|
||||||
|
fireEvent.click(screen.getByTestId("quick-entry-fast-toggle"));
|
||||||
|
expectQuickEntryPrimaryIconCluster();
|
||||||
|
|
||||||
|
for (const taskPriority of TASK_PRIORITIES) {
|
||||||
|
openPriorityMenu();
|
||||||
|
fireEvent.click(screen.getByTestId(`quick-entry-priority-option-${taskPriority}`));
|
||||||
|
expectQuickEntryPriorityButton(taskPriority);
|
||||||
|
expectQuickEntryPrimaryIconCluster();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("renders urgency-colored priority glyphs in the trigger and picker for every level", () => {
|
it("renders urgency-colored priority glyphs in the trigger and picker for every level", () => {
|
||||||
renderQuickEntryBox({});
|
renderQuickEntryBox({});
|
||||||
expandQuickEntry();
|
expandQuickEntry();
|
||||||
|
|||||||
Reference in New Issue
Block a user