FN-7296: show quick-add workflow icons

Improve the quick-add workflow selector so workflow choices are easier to identify before creating a task.

- Render shared workflow icons in the quick-add trigger and workflow option list.
- Widen the workflow selector/menu with tokenized responsive CSS while avoiding empty custom icon shells.
- Cover built-in, custom, and iconless workflow rendering plus widened CSS expectations.
- Add a patch changeset for the published Fusion package.

Files changed:
 .changeset/fn-7296-quick-add-workflow-icons.md     |  7 ++++
 .../dashboard/app/components/QuickEntryBox.css     | 45 ++++++++++++++++++++--
 .../dashboard/app/components/QuickEntryBox.tsx     | 15 +++++++-
 .../components/__tests__/QuickEntryBox.test.tsx    | 41 +++++++++++++++++---
 4 files changed, 98 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-7296

Fusion-Task-Lineage: e493eb27-6957-4fbd-bfb9-903f9ec43b29

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-30 12:47:11 -07:00
parent 658b3511c7
commit 7beb64eba5
4 changed files with 98 additions and 10 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Show workflow icons and wider names in the Quick Add workflow selector.
category: fix
dev: Reuses WorkflowIcon in QuickEntryBox and widens the tokenized selector/menu styles.

View File

@@ -138,15 +138,30 @@ The global `.description-with-refine textarea { padding-right: 70px }` (styles.c
/*
FNXC:QuickAddWorkflow 2026-06-30-00:00:
Quick-add workflow targeting sits in the action row as a compact existing-button dropdown. Use tokenized sizing/colors so the selector wraps with Save/Plan controls on narrow Board and List surfaces without introducing a separate visual hierarchy.
FNXC:QuickAddWorkflow 2026-06-30-12:38:
Operators need workflow identity at quick-add time. Keep the trigger wide enough for realistic names, render shared WorkflowIcon output inline, and let no-icon custom workflows collapse without blank icon shells.
*/
.quick-entry-workflow-wrap {
position: relative;
display: inline-flex;
flex: 1 1 calc(var(--space-xl) * 8);
min-width: 0;
max-width: min(calc(var(--space-xl) * 11), 100%);
}
.quick-entry-workflow-trigger {
max-width: calc(var(--space-xl) * 8);
display: inline-flex;
align-items: center;
justify-content: flex-start;
gap: var(--space-xs);
width: 100%;
min-width: 0;
max-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)));
}
.quick-entry-workflow-icon {
flex: 0 0 auto;
}
.quick-entry-workflow-label {
@@ -160,7 +175,8 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
position: absolute;
inset-block-start: calc(100% + var(--space-xs));
inset-inline-start: 0;
min-width: min(calc(var(--space-xl) * 10), calc(100vw - var(--space-lg)));
width: min(calc(var(--space-xl) * 16), calc(100vw - var(--space-lg)));
min-width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)));
max-width: calc(100vw - var(--space-lg));
max-height: min(calc(var(--space-xl) * 10), calc(100vh - var(--space-xl) * 2));
overflow-y: auto;
@@ -173,6 +189,28 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
background: transparent;
color: inherit;
text-align: left;
align-items: flex-start;
}
.quick-entry-workflow-option-copy {
display: flex;
flex-direction: column;
gap: var(--space-2xs);
min-width: 0;
}
.quick-entry-workflow-option-name,
.quick-entry-workflow-option-id {
min-width: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.quick-entry-workflow-option-id {
color: var(--text-muted);
font-family: var(--font-mono);
}
@media (max-width: 768px) {
@@ -182,7 +220,8 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
}
.quick-entry-workflow-menu {
min-width: min(calc(var(--space-xl) * 9), calc(100vw - var(--space-lg)));
width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)));
min-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)));
}
}

View File

@@ -16,6 +16,7 @@ import { useNodes } from "../hooks/useNodes";
import { NodeHealthDot } from "./NodeHealthDot";
import { ProviderIcon } from "./ProviderIcon";
import { WorkflowOptionalStepsDropdown } from "./WorkflowOptionalStepsDropdown";
import { WorkflowIcon } from "./WorkflowIcon";
const STORAGE_KEY = "kb-quick-entry-text";
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
@@ -309,6 +310,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
}, [realWorkflowOptions]);
const showWorkflowSelector = workflowId !== undefined && realWorkflowOptions.length >= 2 && quickEntryWorkflowId !== null;
const quickEntryWorkflowLabel = selectedQuickEntryWorkflow?.name ?? t("tasks.workflow", "Workflow");
const selectedQuickEntryWorkflowIcon = selectedQuickEntryWorkflow?.icon;
const selectedWorkflowForCreate = workflowId === undefined ? undefined : quickEntryWorkflowId;
useEffect(() => {
@@ -1693,6 +1695,12 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
}}
title={t("tasks.quickEntryWorkflowTitle", "Workflow for the next task")}
>
<WorkflowIcon
workflowId={selectedQuickEntryWorkflow?.id ?? quickEntryWorkflowId ?? ""}
icon={selectedQuickEntryWorkflowIcon}
className="quick-entry-workflow-icon"
decorative
/>
<span className="quick-entry-workflow-label">{quickEntryWorkflowLabel}</span>
<ChevronDown size={12} aria-hidden="true" />
</button>
@@ -1719,8 +1727,11 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
setShowWorkflowPicker(false);
}}
>
<span className="dep-dropdown-title">{option.name}</span>
{duplicateName ? <span className="dep-dropdown-subtitle">{option.id}</span> : null}
<WorkflowIcon workflowId={option.id} icon={option.icon} className="quick-entry-workflow-icon" decorative />
<span className="quick-entry-workflow-option-copy">
<span className="dep-dropdown-title quick-entry-workflow-option-name">{option.name}</span>
{duplicateName ? <span className="dep-dropdown-subtitle quick-entry-workflow-option-id">{option.id}</span> : null}
</span>
</button>
);
})}

View File

@@ -1655,6 +1655,30 @@ describe("QuickEntryBox", () => {
expect(fetchWorkflowOptionalSteps).not.toHaveBeenCalledWith("__all_workflows__", TEST_PROJECT_ID);
});
it("renders built-in and custom workflow icons without blank custom-icon shells", () => {
renderQuickEntryBox({
workflowId: "builtin:coding",
defaultWorkflowId: "builtin:coding",
workflowOptions: [
{ id: "builtin:coding", name: "Coding", columns: [] },
{ id: "wf-custom", name: "Custom workflow", icon: "🚀", columns: [] },
{ id: "wf-no-icon", name: "No icon workflow", columns: [] },
],
});
const trigger = screen.getByTestId("quick-entry-workflow-trigger");
expect(trigger.querySelector(".workflow-icon--builtin")).toBeTruthy();
expect(trigger.querySelector(".quick-entry-workflow-label")).toHaveTextContent("Coding");
fireEvent.click(trigger);
const customOption = screen.getByTestId("quick-entry-workflow-option-wf-custom");
expect(customOption.querySelector(".workflow-icon--custom")).toHaveTextContent("🚀");
expect(screen.getByTestId("quick-entry-workflow-option-wf-no-icon").querySelector(".workflow-icon")).toBeNull();
fireEvent.click(customOption);
expect(screen.getByTestId("quick-entry-workflow-trigger").querySelector(".workflow-icon--custom")).toHaveTextContent("🚀");
});
it("hides the selector without real workflow choices and leaves no shell", () => {
renderQuickEntryBox({ workflowId: "wf-default", workflowOptions: [{ id: "wf-default", name: "Coding", columns: [] }] });
@@ -1662,11 +1686,18 @@ describe("QuickEntryBox", () => {
expect(screen.getByTestId("quick-entry-actions").querySelector(".quick-entry-workflow-wrap")).toBeNull();
});
it("uses tokenized responsive CSS for the workflow selector", () => {
const selectorRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
expect(selectorRule).toContain("var(--space-");
expect(selectorRule).not.toMatch(/#[0-9a-f]{3,8}|rgb\(/i);
expect(QUICK_ENTRY_BOX_CSS).toMatch(/@media \(max-width: 768px\) \{[\s\S]*?quick-entry-workflow/);
it("uses tokenized responsive CSS for the wider workflow selector", () => {
const triggerRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-trigger");
const menuRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
const optionCopyRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-option-copy");
expect(triggerRule).toContain("max-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)))");
expect(triggerRule).toContain("gap: var(--space-xs)");
expect(menuRule).toContain("width: min(calc(var(--space-xl) * 16), calc(100vw - var(--space-lg)))");
expect(menuRule).toContain("min-width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)))");
expect(optionCopyRule).toContain("gap: var(--space-2xs)");
expect(`${triggerRule}\n${menuRule}\n${optionCopyRule}`).not.toMatch(/#[0-9a-f]{3,8}|rgb\(|\d+px/i);
expect(QUICK_ENTRY_BOX_CSS).toMatch(/@media \(max-width: 768px\) \{[\s\S]*?quick-entry-workflow-menu[\s\S]*?min-width: min\(calc\(var\(--space-xl\) \* 11\), calc\(100vw - var\(--space-lg\)\)\)/);
});
});