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: 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. 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 { .quick-entry-workflow-wrap {
position: relative; position: relative;
display: inline-flex; display: inline-flex;
flex: 1 1 calc(var(--space-xl) * 8);
min-width: 0; min-width: 0;
max-width: min(calc(var(--space-xl) * 11), 100%);
} }
.quick-entry-workflow-trigger { .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 { .quick-entry-workflow-label {
@@ -160,7 +175,8 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
position: absolute; position: absolute;
inset-block-start: calc(100% + var(--space-xs)); inset-block-start: calc(100% + var(--space-xs));
inset-inline-start: 0; 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-width: calc(100vw - var(--space-lg));
max-height: min(calc(var(--space-xl) * 10), calc(100vh - var(--space-xl) * 2)); max-height: min(calc(var(--space-xl) * 10), calc(100vh - var(--space-xl) * 2));
overflow-y: auto; overflow-y: auto;
@@ -173,6 +189,28 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
background: transparent; background: transparent;
color: inherit; color: inherit;
text-align: left; 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) { @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 { .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 { NodeHealthDot } from "./NodeHealthDot";
import { ProviderIcon } from "./ProviderIcon"; import { ProviderIcon } from "./ProviderIcon";
import { WorkflowOptionalStepsDropdown } from "./WorkflowOptionalStepsDropdown"; import { WorkflowOptionalStepsDropdown } from "./WorkflowOptionalStepsDropdown";
import { WorkflowIcon } from "./WorkflowIcon";
const STORAGE_KEY = "kb-quick-entry-text"; const STORAGE_KEY = "kb-quick-entry-text";
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]; const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
@@ -309,6 +310,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
}, [realWorkflowOptions]); }, [realWorkflowOptions]);
const showWorkflowSelector = workflowId !== undefined && realWorkflowOptions.length >= 2 && quickEntryWorkflowId !== null; const showWorkflowSelector = workflowId !== undefined && realWorkflowOptions.length >= 2 && quickEntryWorkflowId !== null;
const quickEntryWorkflowLabel = selectedQuickEntryWorkflow?.name ?? t("tasks.workflow", "Workflow"); const quickEntryWorkflowLabel = selectedQuickEntryWorkflow?.name ?? t("tasks.workflow", "Workflow");
const selectedQuickEntryWorkflowIcon = selectedQuickEntryWorkflow?.icon;
const selectedWorkflowForCreate = workflowId === undefined ? undefined : quickEntryWorkflowId; const selectedWorkflowForCreate = workflowId === undefined ? undefined : quickEntryWorkflowId;
useEffect(() => { useEffect(() => {
@@ -1693,6 +1695,12 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
}} }}
title={t("tasks.quickEntryWorkflowTitle", "Workflow for the next task")} 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> <span className="quick-entry-workflow-label">{quickEntryWorkflowLabel}</span>
<ChevronDown size={12} aria-hidden="true" /> <ChevronDown size={12} aria-hidden="true" />
</button> </button>
@@ -1719,8 +1727,11 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
setShowWorkflowPicker(false); setShowWorkflowPicker(false);
}} }}
> >
<span className="dep-dropdown-title">{option.name}</span> <WorkflowIcon workflowId={option.id} icon={option.icon} className="quick-entry-workflow-icon" decorative />
{duplicateName ? <span className="dep-dropdown-subtitle">{option.id}</span> : null} <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> </button>
); );
})} })}

View File

@@ -1655,6 +1655,30 @@ describe("QuickEntryBox", () => {
expect(fetchWorkflowOptionalSteps).not.toHaveBeenCalledWith("__all_workflows__", TEST_PROJECT_ID); 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", () => { it("hides the selector without real workflow choices and leaves no shell", () => {
renderQuickEntryBox({ workflowId: "wf-default", workflowOptions: [{ id: "wf-default", name: "Coding", columns: [] }] }); 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(); expect(screen.getByTestId("quick-entry-actions").querySelector(".quick-entry-workflow-wrap")).toBeNull();
}); });
it("uses tokenized responsive CSS for the workflow selector", () => { it("uses tokenized responsive CSS for the wider workflow selector", () => {
const selectorRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu"); const triggerRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-trigger");
expect(selectorRule).toContain("var(--space-"); const menuRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
expect(selectorRule).not.toMatch(/#[0-9a-f]{3,8}|rgb\(/i); const optionCopyRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-option-copy");
expect(QUICK_ENTRY_BOX_CSS).toMatch(/@media \(max-width: 768px\) \{[\s\S]*?quick-entry-workflow/);
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\)\)\)/);
}); });
}); });